C++ Memory Order | "메모리 순서" 가이드

C++ Memory Order | "메모리 순서" 가이드

이 글의 핵심

C++ Memory Order에 대한 실전 가이드입니다.

메모리 순서란?

멀티스레드에서 메모리 연산 순서

#include <atomic>

std::atomic<int> x{0};
std::atomic<int> y{0};

// Thread 1
x.store(1, std::memory_order_relaxed);
y.store(1, std::memory_order_relaxed);

// Thread 2
int r1 = y.load(std::memory_order_relaxed);
int r2 = x.load(std::memory_order_relaxed);
// r1=1, r2=0 가능 (재정렬)

메모리 순서 종류

// 1. memory_order_relaxed
// - 순서 보장 없음
// - 가장 빠름

// 2. memory_order_acquire/release
// - 획득-해제 의미론
// - 일반적 사용

// 3. memory_order_seq_cst (기본)
// - 순차 일관성
// - 가장 강함

실전 예시

예시 1: relaxed

std::atomic<int> counter{0};

void increment() {
    counter.fetch_add(1, std::memory_order_relaxed);
}

// 여러 스레드에서 호출
// 순서 보장 없지만 원자성 보장

예시 2: acquire-release

std::atomic<bool> ready{false};
int data = 0;

// Thread 1 (Producer)
void produce() {
    data = 42;  // 1
    ready.store(true, std::memory_order_release);  // 2
    // 1이 2보다 먼저 실행 보장
}

// Thread 2 (Consumer)
void consume() {
    while (!ready.load(std::memory_order_acquire)) {}
    // ready가 true면 data = 42 보장
    std::cout << data << std::endl;  // 42
}

예시 3: seq_cst

std::atomic<int> x{0};
std::atomic<int> y{0};

// Thread 1
x.store(1, std::memory_order_seq_cst);
int r1 = y.load(std::memory_order_seq_cst);

// Thread 2
y.store(1, std::memory_order_seq_cst);
int r2 = x.load(std::memory_order_seq_cst);

// r1=0, r2=0 불가능 (순차 일관성)

예시 4: 스핀락

class SpinLock {
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
    
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {
            // 스핀
        }
    }
    
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

acquire-release 의미론

std::atomic<int> data{0};
std::atomic<bool> ready{false};

// Producer
void produce() {
    data.store(42, std::memory_order_relaxed);
    ready.store(true, std::memory_order_release);
    // release 전 모든 쓰기 완료
}

// Consumer
void consume() {
    while (!ready.load(std::memory_order_acquire)) {}
    // acquire 후 모든 읽기 시작
    int value = data.load(std::memory_order_relaxed);
}

자주 발생하는 문제

문제 1: relaxed 오용

// ❌ 순서 의존
std::atomic<int> x{0}, y{0};

// Thread 1
x.store(1, std::memory_order_relaxed);
y.store(1, std::memory_order_relaxed);

// Thread 2
if (y.load(std::memory_order_relaxed) == 1) {
    // x == 1 보장 안됨
}

// ✅ acquire-release
x.store(1, std::memory_order_relaxed);
y.store(1, std::memory_order_release);

if (y.load(std::memory_order_acquire) == 1) {
    // x == 1 보장
}

문제 2: 비원자적 접근

std::atomic<int> x{0};

// ❌ 비원자적 접근
int value = x;  // 암시적 load (seq_cst)

// ✅ 명시적 순서
int value = x.load(std::memory_order_acquire);

문제 3: 성능

// ❌ seq_cst (느림)
counter.fetch_add(1);  // 기본 seq_cst

// ✅ relaxed (빠름)
counter.fetch_add(1, std::memory_order_relaxed);

문제 4: 복잡한 동기화

// 여러 변수 동기화
std::atomic<int> x{0}, y{0};
std::atomic<bool> ready{false};

// Producer
x.store(1, std::memory_order_relaxed);
y.store(2, std::memory_order_relaxed);
ready.store(true, std::memory_order_release);

// Consumer
while (!ready.load(std::memory_order_acquire)) {}
// x, y 모두 보장

성능 비교

// relaxed: 가장 빠름
counter.fetch_add(1, std::memory_order_relaxed);

// acquire/release: 중간
flag.store(true, std::memory_order_release);

// seq_cst: 가장 느림
x.store(1, std::memory_order_seq_cst);

FAQ

Q1: 메모리 순서는?

A: 멀티스레드 메모리 연산 순서 지정.

Q2: 종류는?

A:

  • relaxed: 순서 없음
  • acquire/release: 획득-해제
  • seq_cst: 순차 일관성

Q3: 언제 사용?

A:

  • relaxed: 카운터
  • acquire/release: 동기화
  • seq_cst: 복잡한 동기화

Q4: 성능?

A: relaxed > acquire/release > seq_cst.

Q5: 기본값?

A: seq_cst (가장 안전).

Q6: 메모리 순서 학습 리소스는?

A:

  • “C++ Concurrency in Action”
  • “Memory Ordering at Compile Time”
  • cppreference.com

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ Atomic Operations | “원자적 연산” 가이드
  • C++ Atomic | “메모리 순서” 완벽 가이드
  • C++ Lock-Free Programming | “락 프리 프로그래밍” 가이드

관련 글

  • C++ Atomic Operations |
  • C++ Atomic |
  • C++ Lock-Free Programming |
  • C++ shared_future | 여러 스레드에서 future 결과 공유
  • C++ async & launch |