C++에서 std::atomic을 활용해 멀티스레드 환경에서 발생할 수 있는 경쟁 조건(Race Condition)을 방지하고, 안전하고 정확한 데이터 처리를 구현하는 방법
세 편의 블로그는 공통적으로 std::atomic의 필요성과 작동 원리를 중심으로 설명하며, sum++, sum-- 같은 단순 연산도 원자적이지 않기 때문에 멀티스레드 환경에서는 예상치 못한 결과를 초래할 수 있고, 이를 어떻게 해결할 수 있는지를 다룬다.
sum++, sum-- 같은 간단한 연산도 사실 내부적으로는 메모리에서 값을 읽고 → 수정하고 → 다시 저장하는 3단계의 비원자적 연산으로 구성되어 있다.sum을 읽는 사이에 다른 스레드가 값을 바꾸면, 최종적으로 덮어쓰기가 발생해 값이 꼬이게 된다.std::atomic을 통해 이 원자성을 보장할 수 있다.std::atomic의 동작 방식std::atomic<T>는 T형 변수에 대해 읽기, 쓰기, 덧셈, 뺄셈 등 기본 연산을 원자적으로 처리한다.sum.fetch_add(1)은 내부적으로 sum = sum + 1과 같은 동작을 하지만, 이 전체가 단일한 CPU 명령으로 처리된다.LOCK 프리픽스나 특수한 메모리 배리어(Memory Barrier)를 사용한다.| 용어 | 설명 |
|---|---|
std::atomic<T> | T 타입 변수에 대해 원자성을 보장하는 템플릿 클래스 |
fetch_add(n) | n을 더하는 원자적 연산 |
fetch_sub(n) | n을 빼는 원자적 연산 (fetch_add(-n)과 동일) |
| Race Condition | 두 개 이상의 스레드가 동시에 데이터를 읽고 쓰며 충돌이 발생하는 상황 |
| Atomicity | 연산이 도중에 끊기거나 개입당하지 않고, 완전히 수행되는 특성 |
| All-or-Nothing | atomic 연산은 부분적으로 실행되지 않고 반드시 전부 수행되거나 안 되거나 둘 중 하나다 |
int sum = 0;
void Add()
{
for (int i = 0; i < 100'000; ++i)
{
++sum;
}
}
void Sub()
{
for (int i = 0; i < 100'000; ++i)
{
--sum;
}
}
int main()
{
thread t1(Add);
thread t2(Sub);
t1.join();
t2.join();
cout << "sum : " << sum << endl;
}
sum은 전역 변수이므로 두 스레드가 동시에 접근할 수 있다.+10만 -10만 = 0이 맞지만, 병렬로 실행하면 sum의 결과는 매번 달라진다.++sum, --sum이 다음과 같은 비원자적 명령어 조합으로 이루어지기 때문이다:mov eax, [sum] ; 값 읽기
inc eax ; 증가
mov [sum], eax ; 다시 저장
📌 위 3개의 명령 사이에 다른 스레드가 개입하면 값 충돌 발생
int32 eax = sum;
eax = eax + 1;
sum = eax;
std::atomic<int> sum = 0;
void Add()
{
for (int i = 0; i < 100'000; ++i)
{
sum.fetch_add(1);
}
}
void Sub()
{
for (int i = 0; i < 100'000; ++i)
{
sum.fetch_add(-1);
}
}
int main()
{
thread t1(Add);
thread t2(Sub);
t1.join();
t2.join();
cout << sum << endl;
}
std::atomic<int>를 사용함으로써 sum은 이제 원자적으로 작동한다.fetch_add(n)은 다음과 같은 연산을 한 번에 처리한다:📌 결과는 항상 0이 되며, 정확하게 작동한다.
// sum.fetch_add(1);
// 내부 동작:
// eax = sum; // 읽기
// eax = eax + 1;
// sum = eax; // 쓰기
// → 단, 이 전체가 원자적으로 처리됨!
sum++, sum--)도 실제로는 다단계 명령어로 구성되어 있기 때문에 멀티스레드 환경에서는 충돌 가능성이 있다.std::atomic을 사용하면, 연산 전체를 원자적으로 처리할 수 있다.std::atomic은 그 중 가장 간단하고 강력한 해결책이다.