(C++ : Concurrency) 11. atomic - 1

Posted by : at

Category : Cpp


#include <iostream>
#include <thread>

long x = 0;

void foo()
{
    for(int i = 0; i < 100000; ++i)
    {
        ++x;
    }
}

int main()
{
    std::thread t1(foo);
    std::thread t2(foo);
    std::thread t3(foo);
    // 300000이 결과로 나오게 될까?
    // nope! 스레드 세이프하지 못하다
    t1.join();
    t2.join();
    t3.join();
}
  • 해결방법
    • mutex : OS에서 제공하는 동기화 도구
    • atomic : CPU에서 제공하는 동기화 도구
// ...

void foo()
{
    for(int i = 0; i < 100000; ++i)
    {
        // 기게어로 보자면 아래와 같다
        __asm
        {
            lock inc x
        }
    }
}

// ...
#include <iostream>
#include <thread>
#include <windows.h>

long x = 0;

void foo()
{
    for(int i = 0; i < 100000; ++i)
    {
        InterlockedIncrement(&x);   // windows에서 지원해주는 함수
        /*  // 아래와 동일한 표현
        __asm
        {
            lock inc x
        }
        */
    }
}

그럼 windows에서만 사용이 가능한가?
C++표준에서도 지원한다

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<long> x{0}; // 원자연산이 가능하게 해준다

void foo()
{
    for(int i = 0; i < 100000; ++i)
    {
        ++x;
    }
}

int main()
{
    std::thread t1(foo);
    std::thread t2(foo);
    std::thread t3(foo);
    t1.join();
    t2.join();
    t3.join();
}

// ...

std::atomic<int> x{0};

void foo()
{
    for(int i = 0; i < 100000; ++i)
    {
        // ++x;
        x.fetch_add(1); // 기능은 위와 동일, 메모리 순서를 옵션으로 넣을수 있음
        // x.fetch_add(1, std::memory_order_relaxed);   // 메모리 순서에 대한 설명은 다음에
    }
}

// ...

lock-free

  • OS의 동기화 도구(mutex)를 사용하지 않고
  • CPU Level의 명령어를 사용해 동기화 진행
#include <iostream>
#include <thread>
#include <atomic>

struct Point { int x, y; }
struct Point3D { int x, y, z; }

std::atomic<int> at1;
// 사용자가 만든 구조체도 lock-free로 동작가능할까
std::atomic<Point> at2;
std::atomic<Point3D> at3;

int main()
{
    ++at1;

    std::cout << at1.is_lock_free() << std::endl;       // okay
    std::cout << at2.is_lock_free() << std::endl;       // okay
    std::cout << at3.is_lock_free() << std::endl;       // fail - 64bits를 넘어가기에 lock-free로 동작할순 없고, 그래도 가능하게 만드려면 spin-lock을 사용
}
#include <iostream>
#include <atomic>

struct Point
{
    int x, y;
    Point() = default;

    Point(const Point&) {}  // Error - load시에 복사생성이 되어야하는데 그럼 atomic을 유지할 수 없다
    // 만약 복사생성자가 없다면 에러가 없어진다
};

std::atomic<Point> pt;

int main()
{
    Point ret = pt.load();
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links