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가 나올까?)
}
위 코드 문제점들:
- 가시성 문제: ThreadA에서 변경한 data가 ThreadB에서 언제 보일지 모름
- 코드 재배치 문제: 컴파일러/CPU가 성능을 위해 1,2번 순서를 바꿀 수 있음
- 캐시 문제: 각 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 |