🧩 Atomic 연산이란?

std::atomic<int64_t> v;
std::cout << v.is_lock_free() << '\n'; // 대부분 1 출력 (lock 없이 가능)
  • Atomic 연산은 CPU가 한 번에 연산을 끝내는 것이다.
  • 복합 구조체는 다음과 같이 lock이 필요해 atomic이 아닌 경우도 있다:
struct Knight { int64 hp, mp, exp; };
std::atomic<Knight> knight;
std::cout << knight.is_lock_free() << '\n'; // 0 출력

🧠 즉, atomic이라고 선언해도 컴파일러와 하드웨어가 지원하지 않으면 내부적으로 mutex lock을 사용할 수 있다!


💣 코드 재배치란?

  • 컴파일러는 최적화를 위해 코드 순서를 바꿀 수 있다.
  • CPU도 파이프라인 구조로 인해 명령어 순서를 바꿔 실행할 수 있다.
  • 이런 현상은 멀티 쓰레드 환경에서 심각한 버그를 초래할 수 있다.

예제:

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은 성능 차이 큼 → 적절한 모델 선택 필수.


🧪 실전 예제: Compare-And-Swap (CAS)

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_weakspurious failure(헛 실패) 가능성이 있어 루프 안에서 반복 시도해야 한다.
compare_exchange_strong은 덜 실패하지만 성능 차이는 크지 않다.


✍️ C++11 이후 가장 중요한 개념: 메모리 모델

"atomic 연산에 한해, 모든 쓰레드는 동일한 수정 순서를 관찰한다."

❓ '동일한 순서를 관찰'이란?

  • 0 → 2를 봤다면, 다시 0을 관찰할 수는 없다.
  • 그러나 1, 5 등을 거치지 않고 0 → 2 → 5가 되는 것은 허용된다.
  • 즉, 시간의 흐름은 역행할 수 없지만 모든 변화를 관찰할 의무는 없다.

🧱 코드 fence: atomic 없이 절취선 긋기

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 Storage (TLS)

thread_local int32_t tl = 0;
  • thread_local은 전역/정적 변수를 스레드마다 독립적으로 관리하고 싶을 때 사용한다.
  • 하나의 전역 변수처럼 보이지만, 스레드마다 별도의 인스턴스를 가지므로 안전하다.

profile
李家네_공부방

0개의 댓글