🔹 std::atomic 개념과 활용
std::atomic은 C++에서 멀티스레드 환경에서의 동기화 없이 원자적 연산을 수행할 수 있도록 제공되는 템플릿 클래스입니다.
이를 통해 Lock-Free 또는 Wait-Free 알고리즘을 구현할 수 있으며, CPU의 원자적 연산을 직접 활용하여 성능을 높일 수 있습니다.
✅ 1. std::atomic의 특징
✔ 멀티스레드 안전(Thread-safe)
✔ Lock-Free 지원 (하드웨어 CAS 등 사용)
✔ 메모리 순서 제어 가능 (memory_order 옵션)
✔ 기본 데이터 타입, 사용자 정의 타입에도 적용 가능
✅ 2. std::atomic 기본 사용법
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 10000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed); // 원자적 증가
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final Counter: " << counter.load() << std::endl;
}
✅ 설명:
- std::atomic<int>을 사용하여 동기화 없이 원자적으로 증가.
- fetch_add(1, std::memory_order_relaxed)를 사용해 락 없이 counter를 증가.
- 두 개의 스레드가 동시에 증가 작업을 수행해도 데이터 경쟁(Race Condition) 없이 안전하게 동작.
✅ 3. 주요 멤버 함수
함수 | 설명 |
load() | 원자적으로 변수 값을 읽음 |
store(value) | 원자적으로 변수 값을 설정 |
fetch_add(n) | += n 연산을 원자적으로 수행 |
fetch_sub(n) | -= n 연산을 원자적으로 수행 |
fetch_and(n) | &= n 연산을 원자적으로 수행 |
fetch_or(n) | |= n 연산을 원자적으로 수행 |
fetch_xor(n) | ^= n 연산을 원자적으로 수행 |
compare_exchange_weak(expected, desired) | CAS(Compare-And-Swap) 연산 수행 |
compare_exchange_strong(expected, desired) | CAS 연산 수행 (더 강력한 보장) |
✅ 4. compare_exchange_weak (CAS 연산)
🔹 **CAS(Compare-And-Swap)**은 Lock-Free 알고리즘에서 핵심적으로 사용됩니다.
🔹 compare_exchange_weak()를 활용하여 값이 예상한 값(expected)일 때만 새로운 값(desired)으로 변경할 수 있습니다.
🔹 실패할 경우 expected가 현재 값으로 업데이트되므로 다시 시도 가능.
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> value(10);
void cas_example() {
int expected = 10;
int desired = 20;
if (value.compare_exchange_weak(expected, desired)) {
std::cout << "CAS 성공! value = " << value.load() << std::endl;
} else {
std::cout << "CAS 실패! 현재 value = " << value.load() << std::endl;
}
}
int main() {
std::thread t1(cas_example);
std::thread t2(cas_example);
t1.join();
t2.join();
return 0;
}
✅ 설명
- compare_exchange_weak(expected, desired)는 expected 값과 현재 값이 같을 경우만 값을 변경.
- 스레드 간 충돌이 발생하면 CAS가 실패할 수 있으며, 이를 기반으로 Lock-Free 알고리즘을 구성할 수 있음.
✅ 5. 메모리 순서 (memory_order)
🔹 std::atomic 연산은 기본적으로 메모리 순서(memory_order) 를 지정할 수 있음.
🔹 기본값은 memory_order_seq_cst (Sequentially Consistent Order) 이지만, 성능 최적화를 위해 더 약한 순서를 사용할 수 있음.
메모리 순서 | 설명 | 성능 |
memory_order_seq_cst | 가장 강력한 순서 보장 (기본값) | 느림 |
memory_order_acquire | 읽기 전에 이전 연산들이 완료되도록 보장 | 중간 |
memory_order_release | 쓰기 이후 연산들이 순서대로 실행되도록 보장 | 중간 |
memory_order_acq_rel | acquire + release 조합 | 중간 |
memory_order_relaxed | 메모리 순서 보장 없음 (최대 성능) | 빠름 |
🔹 예제 (memory_order_relaxed 사용)
std::atomic<int> value[5] = { 0, };
void thread_func()
{
value[0].store(1, std::memory_order_relaxed);
value[1].store(2, std::memory_order_relaxed);
value[2].store(3, std::memory_order_relaxed);
value[3].store(4, std::memory_order_relaxed);
value[4].store(5, std::memory_order_relaxed);
// std::memory_order_relaxed로 사용된 경우 어떤 변수가 먼저 수행될지 보장 되지 않는다.
// 0보다 1번이 먼저 연산될 수도 있음
// 컴파일러나 CPU의 최적화에 따라 value[0]이 value[1]보다 먼저 저장될 수도 있고,
// 반대일 수도 있음
}
✅ memory_order_relaxed를 사용하면 순서는 보장하지 않지만 원자적 연산은 보장됨.
✅ 따라서 일부 Lock-Free 알고리즘에서 성능 최적화를 위해 활용 가능.
✅ 6. std::atomic을 활용한 Lock-Free 스택 구현
#include <iostream>
#include <atomic>
struct Node {
int data;
Node* next;
};
class LockFreeStack {
std::atomic<Node*> head;
public:
void push(int value) {
Node* new_node = new Node{value, nullptr};
while (true) {
Node* old_head = head.load();
new_node->next = old_head;
if (head.compare_exchange_weak(old_head, new_node)) {
return;
}
}
}
bool pop(int& value) {
while (true) {
Node* old_head = head.load();
if (!old_head) return false;
if (head.compare_exchange_weak(old_head, old_head->next)) {
value = old_head->data;
delete old_head;
return true;
}
}
}
};
✅ 설명:
- compare_exchange_weak을 사용하여 Lock-Free 방식으로 스택을 구현.
- while (true) 루프에서 원자적 연산을 사용해 충돌이 발생해도 재시도할 수 있도록 처리.
✅ 7. std::atomic과 FAA(Fetch-And-Add)
🔹 fetch_add() 연산은 FAA(Fetch-And-Add)라고도 하며, 원자적으로 값을 증가시키는 연산입니다.
🔹 일부 CPU에서는 FAA가 CAS보다 성능이 뛰어나므로 Lock-Free 카운터 등에 사용됩니다.
std::atomic<int> counter(0);
void increment() {
counter.fetch_add(1, std::memory_order_relaxed);
}
✅ fetch_add는 락 없이 안전하게 증가 연산을 수행.
✅ 8. 정리
✔ std::atomic을 사용하면 락 없이 동기화가 가능한 원자적 연산 수행 가능
✔ compare_exchange_weak (CAS)을 이용하여 Lock-Free 알고리즘 구현 가능
✔ fetch_add()는 FAA(Fetch-And-Add) 연산을 통해 락 없이 카운터 증가 가능
✔ memory_order를 조절하여 성능 최적화 가능 (memory_order_relaxed 등)
📌 즉, std::atomic은 Lock-Free & Wait-Free 알고리즘을 구현할 때 핵심적인 역할을 함! 🚀
reference
https://narakit.tistory.com/194
[C++ Thread] Lock-Free Programming - (1) Lock Free ? / Lock Free Stack
Lock-Free 코드란? - Concurrency와 Scalibility를 가지는 동기화 방법 코드상 존재하는 블로킹/기다림을 제거하거나 줄이는 것 - 블록킹을 사용하면서 생기는 잠재적인 문제 경쟁 조건 (Race Condition): 잘못
narakit.tistory.com
Introduction to Lock-Free and How it is used to implement Thread-safe Non-Blocking Queue in Java
Introduction
medium.com
씹어먹는 C++ - <15 - 3. C++ memory order 와 atomic 객체>
여러분의 코드는 여러분이 생각하는 순서로 작동하지 않습니다. (단일 쓰레드 관점에서) 결과값이 동일하다면 컴파일러와 CPU 는 명령어의 순서를 재배치 할 수 있습니다. 문제는 이렇게 마음대
modoocode.com
https://en.cppreference.com/w/cpp/thread
Concurrency support library (since C++11) - cppreference.com
Concurrency support library C++ includes built-in support for threads, atomic operations, mutual exclusion, condition variables, and futures. [edit] Threads Threads enable programs to execute across several processor cores. manages a separate thread (class
en.cppreference.com
'프로그래밍 > C,C++' 카테고리의 다른 글
[Modern C++] decltype (0) | 2025.03.25 |
---|---|
[Modern C++] STL 자료구조 (0) | 2025.03.24 |
[Modern C++] 동기화 기법(Synchronization) (0) | 2025.03.20 |
[Modern C++] std::thread (0) | 2025.03.11 |
[Modern C++] smart pointer(unique_ptr, shared_ptr, weak_ptr) (0) | 2025.03.10 |