1. 경쟁 조건(Race Condition)

(1) 코드 실험

int sum = 0;

void Add()
{
	for (int i = 0; i < 100'0000; i++)
		sum++;
}
void Sub()
{
	for (int i = 0; i < 100'0000; i++)
		sum--;
}

int main()
{
	thread t1(Add);
	thread t2(Sub);

	t1.join();
	t2.join();

	cout << sum << endl;
}

위의 코드를 실행하면 0이 나와야 하는 것이 당연하다.

하지만 결과값은 0이 아니다. 심지어 실행할 때마다 값이 달라진다.

이는 Add 함수가 100만 번 실행된 후 Sub 함수가 100만 번 실행된 것이 아니라 두 스레드가 번갈아가며 각 함수를 실행했기 때문이다.

(레지스터의 값을 메모리에 저장하기 직전에 다른 함수에서 구한 값이 끼어들기 때문에 발생하는 문제이다)

 

이를 공유 자원인 변수 sum에 대해서 경쟁 조건(Race Condition)이 발생했다고 한다.

 

 

2. 원자적 연산(atomic)

#include <atomic>
atomic<int> sum = 0;

void Add()
{
	for (int i = 0; i < 100'0000; i++)
	{
		sum++;
	}
}
void Sub()
{
	for (int i = 0; i < 100'0000; i++)
	{
		sum--;
	}
}

int main()
{
	thread t1(Add);
	thread t2(Sub);

	t1.join();
	t2.join();

	cout << sum << endl;
}

이제는 sum을 atmic<int>로 선언하여 실험을 진행하면 정상적으로 0이 뜨는 것을 볼 수 있다.

한 스레드가 완전히 실행되거나 실행되지 않을 때까지 다른 스레드에게 방해받지 않게하는 방법이다.

 

(1) load 함수

//int temp = sum;
int temp = sum.load();

값을 불러온다.

주석의 방식과 동일하게 작동한다. 다만 atomic 형식을 이용한다는 것을 명시적으로 알린다.

 

(2) store 함수

//sum = 10;
sum.store(10);

변수에 값을 저장한다.

주석의 방식과 동일하게 작동한다. 다만 atomic 형식을 이용한다는 것을 명시적으로 알린다.

 

(3) exchange 함수

int temp = sum.exchange(10);

sum에 10을 대입하고, 그 이전의 값을 temp에 저장한다.

 

(4) fetch_add 함수

int prev = sum.fetch_add(10);

sum의 값을 가져와 10을 더하고, 그 이전의 값을 temp에 저장한다.

 

아토믹을 사용하여 공유 자원을 안전하게 보장할 수 있다.

다만, 일반적으로 아토믹 타입은 일반 타입에 비해 느리므로 사용에 주의가 필요하다.