static int x = 0;
static int y = 0;
static int r1 = 0;
static int r2 = 0;
static void Thread_1()
{
y = 1;
r1 = x;
}
static void Thread_2()
{
x = 1;
r2 = y;
}
static void Main(string[] args)
{
int count = 0;
while(true)
{
count++;
x = y = r1 = r2 = 0;
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
// 과연 빠져나오는 경우가 있을까?
if(r1 == 0 && r2 == 0)
break;
}
// 생각보다 금방 빠져나온다 (보통 3~5번만에)
Console.WriteLine($"{count}번만에 빠져나옴");
}
이게 가능한가???
// CPU입장에서는 아래의 연산들이
static void Thread_1()
{
// y = 1;과 r1 = x;연산의 연관성이 없기에
// r1 = x;를 먼저 실행하고 y = 1;을 실행하는 경우도 발생
y = 1;
r1 = x;
}
static void Thread_2()
{
// 여기도 마찬가지
x = 1;
r2 = y;
}
// 그럼 r1, r2가 모두 0이 나올수 있다
이런식으로 CPU에서 마음대로 최적화를 해버리는데 이를 방지해 보자(->메모리 배리어)
- 코드재배치 억제
- 가시성 향상
static void Thread_1()
{
y = 1;
Thread.MemoryBarrier();
// Thread.MemoryBarrier(); : Store, Load를 모두 막는다
r1 = x;
}
static void Thread_2()
{
x = 1;
Thread.MemoryBarrier();
r2 = y;
}
- 사실
Thread.MemoryBarrier()
를 직접적으로 쓰게 되는 경우는 드물다 - 이런식으로 메모리 배리어를 두고,
Lock
등이 구현된다고 알고있자
마지막으로 예제하나만 보고 넘어가자
int _answer;
bool _complete;
void A()
{
_answer = 123;
Thread.MemoryBarrier(); // 여기는 왜 사용되는지 알겠고
_complete = true;
Thread.MemoryBarrier();
// 두 번째 배리어의 경우
// _complete = true; 이후 Flush 메모리에 올리는 것을 진행해 달라는 요청
}
void B()
{
// 여기 배리어의 경우
// if문의 _complete를 읽기위해서 최신의 메모리를 읽어와 달라는 요청
Thread.MemoryBarrier();
if(_complete)
{
// 여기는 상동
Thread.MemoryBarrier();
Console.WriteLine(_answer);
}
}