아래 예제는 add함수에서 100만번 ++하고, sub함수에서 100만번 --한다. 결과는 당연히 0이된다.

하지만 add, sub함수를 각 스레드로 만들어 진행하면, 값은 보장받지 못한다.

//

#include <iostream>
#include <thread>

using namespace std;

int sum = 0;

void add()
{
	for (int i = 0; i < 1'000'000; ++i)
	{
		++sum;
	}
}

void sub()
{
	for (int i = 0; i < 1'000'000; ++i)
	{
		--sum;
	}
}

int main()
{
	add();
	sub();
	cout << sum << '\\n';

	std::thread t1(add);
	std::thread t2(sub);
	t1.join();
	t2.join();
	cout << sum << '\\n';

	return 0;
}

코드상에서 한줄이라도, 어셈블리에서는 여러줄일 수 있다. CPU는 메모리 값을 꺼내고 연산하는 작업을 한번에 할수 없기 때문에 명령어가 많아지게되고, 이 상황에서 명령어가 같이 돌게되면 값을 보장받을 수 없게된다.

//

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

using namespace std;

//int sum = 0;
atomic<int>sum = 0;

void add()
{
	for (int i = 0; i < 1'000'000; ++i)
	{
		sum.fetch_add(1);
		//++sum;
	}
}

void sub()
{
	for (int i = 0; i < 1'000'000; ++i)
	{
		sum.fetch_sub(1);
		//--sum;
	}
}

int main()
{
	add();
	sub();
	cout << sum << '\\n';

	std::thread t1(add);
	std::thread t2(sub);
	t1.join(); 
	t2.join();
	cout << sum << '\\n';

	return 0;
}

atomic 객체로 thread-safe하게 만든 모습. 윈도우는 Interlocked를 사용하지만, thread객체를 쓰는 이유와 마찬가지로 atomic을 사용한다.

당연하지만 atomic 연산은 일반 연산보다 느리기 때문에 남용하면 안될 것.