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에 저장한다.
아토믹을 사용하여 공유 자원을 안전하게 보장할 수 있다.
다만, 일반적으로 아토믹 타입은 일반 타입에 비해 느리므로 사용에 주의가 필요하다.
'서버 프로그래밍 > 멀티 스레드' 카테고리의 다른 글
1-6. CAS(Compare And Swap), 스핀 락(Spin Lock) (0) | 2023.11.24 |
---|---|
1-5. 락 (Lock), 뮤텍스(mutex), RAII 패턴, lock_guard (0) | 2023.11.24 |
1-3. 캐시, CPU 파이프라인, 스레드 경쟁 조건 (1) | 2023.11.23 |
1-2. 멀티 스레드 (0) | 2023.11.23 |
1-1. 게임 서버란? (0) | 2023.11.23 |