std::atomic<int64_t> v;
std::cout << v.is_lock_free() << '\n'; // 대부분 1 출력 (lock 없이 가능)
struct Knight { int64 hp, mp, exp; };
std::atomic<Knight> knight;
std::cout << knight.is_lock_free() << '\n'; // 0 출력
🧠 즉, atomic이라고 선언해도 컴파일러와 하드웨어가 지원하지 않으면 내부적으로 mutex lock을 사용할 수 있다!
예제:
std::atomic<bool> ready = false;
int value = 0;
void Producer() {
value = 10;
ready.store(true, std::memory_order_release); // release 절취선
}
void Consumer() {
while (!ready.load(std::memory_order_acquire)); // acquire 절취선
std::cout << value << '\n';
}
🔐 release-acquire 패턴은
value = 10이 절취선 이후로 이동하지 못하게 하고, Consumer는 그 이전 값을 보장받는다.
| 메모리 모델 | 특징 |
|---|---|
memory_order_seq_cst | 가장 엄격하고 직관적이다. 가시성 & 재배치 문제 자동 해결 |
memory_order_acquire / release | 절취선 역할을 한다. 가시성 확보 + 제한된 최적화 허용 |
memory_order_relaxed | 제약이 없음. 성능은 좋지만 가시성 & 재배치 문제 발생 가능 |
✅ Intel/AMD는 내부적으로 순차적 일관성을 제공해
seq_cst를 써도 성능에 영향이 거의 없음.
❗ ARM은 성능 차이 큼 → 적절한 모델 선택 필수.
std::atomic<bool> flag = false;
// 기존 값을 prev에 저장하고, 새로운 값으로 교체
bool prev = flag.exchange(true);
// CAS: 조건부로 flag 값을 교체
bool expected = false;
bool desired = true;
flag.compare_exchange_weak(expected, desired);
compare_exchange_weak는 spurious failure(헛 실패) 가능성이 있어 루프 안에서 반복 시도해야 한다.
compare_exchange_strong은 덜 실패하지만 성능 차이는 크지 않다.
"atomic 연산에 한해, 모든 쓰레드는 동일한 수정 순서를 관찰한다."
0 → 2를 봤다면, 다시 0을 관찰할 수는 없다.1, 5 등을 거치지 않고 0 → 2 → 5가 되는 것은 허용된다.void Producer() {
value = 10;
std::atomic_thread_fence(std::memory_order_release); // 절취선
}
void Consumer() {
std::atomic_thread_fence(std::memory_order_acquire); // 절취선
std::cout << value << '\n';
}
🔧
std::atomic_thread_fence는 atomic 변수 없이도 메모리 재배치를 제어할 수 있는 도구다.
thread_local int32_t tl = 0;