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;
}
Console.WriteLine($"{count}번 작동");
}
위의 코드를 실행하면 while문이 103번이 작동함을 알 수 있었다.
이는 쓰레드에서 y와 r1, 그리고 x와 r2가 연관이 없기에 하드웨어에서 최적화를 하기 때문이다.
static void Thread_1()
{
r1 = x;
y = 1;
}
static void Thread_2()
{
r2 = y;
x = 1;
}
즉 위의 방식으로 작동했다는 의미가 된다.
Thread.MemoryBarrier();
각 Thread에서 Load와 Store하는 문장 사이에 위의 코드를 작성한다.
기대한 무한루프가 작동되는 것을 확인하였다.
이는 Thread.MemoryBarrier()는 코드 재배치를 억제하는 기능이기 때문이다.
메모리 배리어는 코드의 재배치 뿐만 아니라 가시성도 챙겨준다.
int _answer;
bool _complete;
void A()
{
_answer = 123;
Thread.MemoryBarrier();
_complete = true;
Thread.MemoryBarrier();
}
void B()
{
Thread.MemoryBarrier();
if (_complete)
{
Thread.MemoryBarrier();
Console.WriteLine(_answer);
}
}
static void Main(string[] args)
{
}
A 에서 _answer의 값을 write 해주고 MemoryBarrier()을 실행하면 해당 값을 메모리에도 갱신하는 효과가 있다.
즉 캐쉬의 값을 바로 가져오지 않고 메모리에 있는 값을 확인하여 로드하게 된다.