서버/멀티쓰레드 프로그래밍

메모리 모델

season97 2025. 5. 30. 14:49
728x90
반응형

캐시와, CPU 파이프라인의 구조로 인해..

  • 멀티스레딩 환경에서 여러 스레드가 메모리에 접근할 때 가시성과 코드 재배치 현상때문에 문제가 생긴다고 했다.
  • 그래서 여러 스레드가 메모리에 접근할 때 어떤 규칙을 따를지 정하는 약속을 메모리 모델 이라고 한다
// 스레드 A
int data = 0;
bool ready = false;

void ThreadA() 
{
    data = 42;        // 1. 데이터 설정
    ready = true;     // 2. 준비 완료 신호
}

void ThreadB() 
{
    while (!ready) 
    {  // 3. 준비될 때까지 대기
        // 대기중...
    }
    cout << data;     // 4. 데이터 출력 (42가 나올까?)
}

위 코드 문제점들:

  1. 가시성 문제: ThreadA에서 변경한 data가 ThreadB에서 언제 보일지 모름
  2. 코드 재배치 문제: 컴파일러/CPU가 성능을 위해 1,2번 순서를 바꿀 수 있음
  3. 캐시 문제: 각 CPU 코어마다 별도 캐시가 있어서 동기화 안될 수 있음

메모리 모델의 3가지 레벨

1. Sequential Consistency (seq_cst) - 가장 엄격

atomic<bool> ready;
atomic<int> data;

void ThreadA() 
{
    data.store(42, memory_order_seq_cst);
    ready.store(true, memory_order_seq_cst);
}

void ThreadB() 
{
    while (!ready.load(memory_order_seq_cst)) {}
    cout << data.load(memory_order_seq_cst); // 항상 42 출력 보장!
}
  • 모든 스레드가 동일한 순서로 메모리 변경을 관찰
  • 마치 단일 CPU에서 실행되는 것처럼 동작
  • 가장 안전하다. (인텔,AMD 상용 CPU는 최적화가 잘 되어있어서 그냥 이 옵션 쓰면 된다)
  • 아무 처리도 하지 않았다면 기본적으로 이 옵션이 들어간다. (가시성o 순서o)

2. Acquire-Release - 중간단계

void Producer() 
{
    data = 42;  // 일반 변수도 OK!
    ready.store(true, memory_order_release); // 🚩 절취선!
}

void Consumer() 
{
    while (!ready.load(memory_order_acquire)) {} // 🚩 절취선!
    cout << data; // 42 출력 보장!
}
  • release : 이 지점 이전의 모든 메모리 연산이 완료되었다 라고 선언하는것
  • acquire : 이 지점 이후 모든 메모리 연산은 이전 연산들이 보인 후에 실행하는 것
  • 이 한 쌍이 동기화 지점을 생성하는 것

3. Relaxed - 가장 자유로움

atomic<int> counter{0};

void increment() 
{
    counter.fetch_add(1, memory_order_relaxed);
}
  • 해당 변수에 대한 원자성만 보장
  • 다른 메모리 연산과의 순서는 보장 안함
  • 성능은 좋다. 다 직접 구현해야한다

문제 없는 코드 예시

atomic<bool> ready;
int32 value;

void Producer() 
{
	value = 10;
	ready.store(true, memory_order::memory_order_seq_cst);
}

void Consumer() 
{
	while (ready.load(memory_order::memory_order_seq_cst) == false)
	{
		;
	}
	
	cout << value << endl;
}

int main()
{
	ready = false;
	value = 0;

	thread t1(Producer);
	thread t2(Consumer);
	t1.join();
	t2.join();
}

※ 결론 :

  • 메모리 모델은 멀티스레드 환경에서 메모리 접근 순서와 가시성을 제어하는 규칙. 안전성과 성능 사이의 균형을 맞추기 위해 여러 레벨을 제공하지만..
  • 인텔이나 AMD의 경우 애당초 순차적 일관성을 보장을 해서 memory_order_seq_cst을 써도 상관 없다
  • ARM같은 경우 꽤 의미있는 차이가 있다고 한다. store에 아무 옵션도 안넣으면 기본적으로 memory_order_seq_cst가 들어가 있다.
728x90
반응형

'서버 > 멀티쓰레드 프로그래밍' 카테고리의 다른 글

LockQueue LockStack  (0) 2025.06.02
TLS (Thread Local Storage)  (0) 2025.06.02
CPU 파이프라인  (0) 2025.05.30
CPU 캐시  (0) 2025.05.30
단발성 처리 future, promise, packaged_task  (0) 2025.05.29