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++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.