다음의 코드를 살펴보자.
int32 x = 0;
int32 y = 0;
int32 r1 = 0;
int32 r2 = 0;
volatile bool ready;
void Thread_1()
{
while (!ready)
;
y = 1; // store y
r1 = x; // load x
}
void Thread_2()
{
while (!ready)
;
x = 1; // store x
r2 = y; // load r2
}
int main()
{
int32 count = 0;
while (true)
{
ready = false;
count++;
x = y = r1 = r2 = 0;
thread t1(Thread_1);
thread t2(Thread_2);
ready = true;
t1.join();
t2.join();
if (r1 == 0 && r2 == 0)
break;
}
cout << count << " 번만에 빠져나옴~" << endl;
}
그냥 코드를 따라 생각해보면, r1과 r2 값이 0일 수 없다고 생각한다. 그러나 실제로 돌려보면 수백 수천번 반복 후 빠져나온다. 이유는 가시성과 코드 재배치 때문이다.
CPU에는 코어마다 캐시가 있다. 값을 읽어올 때 캐시에 해당 값이 있으면 메모리에서 가져오지 않기 때문에 위와 같은 현상이 일어날 수 있다.
그러나 모든 변수에 volatile을 붙여 컴파일 최적화를 막아도 반복문을 빠져나오는데 이는 코드 재배치 때문이다.
컴파일 레벨 또는 CPU 명령 수행 레벨에서 코드 store, load의 순서를 바꾸는게 더 유리하다고 판단되면 코드의 위치를 바꾼다. 그래서 값이 0이 나온다.
CPU 일 처리가 빨래하는 것이라고 가정해보자. 위처럼 빨래가 여러 개 있고 각 세탁, 건조, 다림질, 널어놓기의 과정을 거쳐야 빨래가 완료된다. 이 때 하나의 빨래감이 모두 빨래되길 기다렸다가 다음 빨래를 하는 것보다 하나의 단계를 넘어가면 다른 빨래를 시작하는 것이 훨씬 작업속도가 빠르다.
하지만 빨래의 양이 항상 똑같은건 아니다. CPU는 작업의 양에 따라 유리하게 작업 순서를 바꿀 수 있다.
waiting instructions 순서대로 pipeline에 들어오지 않는다.
Thread local storage (0) | 2021.08.11 |
---|---|
메모리 모델 (0) | 2021.08.10 |
캐시 (0) | 2021.08.07 |
Future (0) | 2021.08.07 |
Condition variable (0) | 2021.08.07 |
댓글 영역