35. volatile 의 의미와 오해 (MMIO/신호용이지 스레드 동기화가 아니다)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C++
// 주제: volatile 의 진짜 의미와 흔한 오해 (멀티스레드 동기화가 아니다)
//
// 많은 게임 서버 코드가 "스레드 간 공유 플래그/카운터"를 volatile 로 선언해 놓고
// 안전하다고 착각한다. 이 문제는 volatile 이 무엇을 보장하고 무엇을 보장하지
// 않는지를 실측으로 확인한다.
//
// 과제:
//  1) 아래 프로그램에서 두 스레드가 각각 N번 vcounter(volatile)와 acounter(atomic)를
//     증가시킨다. 최종 vcounter 와 acounter 는 각각 2N 이 될까? 이유를 설명하라.
//  2) volatile 이 실제로 보장하는 것(왜 존재하는가)과, 멀티스레드에서 보장하지
//     않는 것(원자성/가시성 순서/happens-before)을 구분해 설명하라.
//  3) C++ 에서 스레드 간 공유 상태에 써야 하는 올바른 도구는 무엇인가?
//
// (동작을 예측한 뒤 컴파일/실행해 확인하라. 내부 동작 설명은 answer.md 참조.)

#include <atomic>
#include <cstdio>
#include <thread>

constexpr long N = 2'000'000;

volatile long vcounter = 0;
std::atomic<long> acounter{0};

void bumpVolatile() { for (long i = 0; i < N; ++i) vcounter = vcounter + 1; }
void bumpAtomic()   { for (long i = 0; i < N; ++i) acounter.fetch_add(1, std::memory_order_relaxed); }

int main() {
    std::thread t1(bumpVolatile), t2(bumpVolatile);
    t1.join(); t2.join();

    std::thread t3(bumpAtomic), t4(bumpAtomic);
    t3.join(); t4.join();

    std::printf("expected      = %ld\n", 2 * N);
    std::printf("volatile got  = %ld\n", vcounter);
    std::printf("atomic got    = %ld\n", acounter.load());
    return 0;
}
내 리뷰 · C++
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.