Atomic

·2023년 12월 19일
0

GameServer

목록 보기
2/2

멀티 스레드 환경에서 공유 데이터를 사용할 때 문제점

#include <thread>

int32 sum = 0;

void Add()
{
	// 스택 메모리 영역에 올라가는 부분임
	for (int32 = 0; i < 1'000'000; i++)
	{
		sum++;
	}
}

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

int main()
{
	Add();
	Sub();
	// main thread에서 실행하면 당연히 0이 나옴
	cout << sum << endl;

	std::thread t1(Add);
	std::thread t2(Sub);
	// 그러나 이렇게 실행하면 엉뚱한 값이 나옴
	cout << sum << endl;
}

멀티 스레드 환경에서 stack은 각기 자신의 영역을 가짐
그러나 힙이나 데이터 영역 같은 경우에는 공유함
(현재 sum은 전역이므로 데이터 영역에 있음 → 공유되는 데이터임)

디스어셈블리로 보면 아래와 같이 sum 함수 내부의 sum++은 세 가지 과정을 거침

어셈블리어는 오른쪽부터 읽으면 됨
sum이라는 애를 가져와서 eax로 옮김
eax에 1을 더함
eax의 값을 sum으로 옮김

마찬가지로 Sub()의 sum--;도 같은 과정을 거침
즉 3 단계를 거치는 동안 다른 쓰레드도 작업을 할 수 있음

int32 sum = 0;

void Add()
{
	int32 eax = sum; // (1) 여기 까지 실행되고
	eax = eax + 1; // 0 + 1 = 1
	sum = eax; // sum = 1
}

void Sub()
{
	int32 eax = sum; // (2) 얘가 실행되면
	eax = eax - 1; // 0 - 1 = -1
	sum = eax; // sum = -1
}

이런 식으로 공용으로 사용되는 데이터의 값이 원하는 대로 작동하지 않을 수 있음

동기화 기법 - atomic

가장 작은 단위 → 다 실행되거나 다 실행되지 않는 것을 atomic이라고 함 (All or Nothing)

#include <atomic>
// int32 sum = 0;
atomic<int32> sum = 0; // atomic은 템플릿 클래스

void Add()
{
	// 스택 메모리 영역에 올라가는 부분임
	for (int32 = 0; i < 1'000'000; i++)
	{
		// sum++; 이렇게 써도 되지만 해당 변수가 atomic 타입인 것을 알려주기 위해 아래와 같이도 사용함
		sum.fetch_add(1);
	}
}

void Sub()
{
	for (int32 = 0; i < 1'000'000; i++)
	{
		// sum--;
		sum.fetch_add(-1);
	}
}

그러나 atomic은 많은 연산을 필요로 함

그러므로 모든 경우에 atomic을 사용해서는 안 되고 진짜 필요할 때만 사용해야 함

0개의 댓글