#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();
}