아래 코드를 실행해보면 쓰레드를 무리없이 빠져나오는 것을 볼 수 있다.
namespace SeverCore
{
class Program
{
static int x = 0;
static int y = 0;
static int r1 = 0;
static int r2 = 0;
static void Thread_1()
{
y = 1; // Store y
r1 = x; // Load x
}
static void Thread_2()
{
x = 1; // Store x
r1 = y; // Load 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}번만에 빠져나옴!");
}
}
}

어떻게 이러한 일이 가능한 것일까?
위 코드가 아래와 같이 맘대로 변경이 된다는 것이다.
x = 0, y = 0
r1 = x r2 = y
y = 1 x = 1
그렇다면 해결방법은 무엇일까?
메모리 배리어의 기능은 다음과 같다.
A) 코드 재배치 억제
B) 가시성
이제 코드를 재배치 하지 않도록 아래와 같이 코드를 수정해준다.
또한 각자의 변수가 최신의 값을 가질 수 있도록 하는 역할도 한다.
Store를 한 뒤 -> 메모리 배리어를 사용
메모리 배리어를 사용한 뒤 -> Load를 함
volatile 키워드를 이용해 가시성을 해결할 수도 있다.
namespace SeverCore
{
class Program
{
static volatile int x = 0;
static volatile int y = 0;
static volatile int r1 = 0;
static volatile int r2 = 0;
static void Thread_1()
{
y = 1; // Store y
//-------------------------
Thread.MemoryBarrier();
r1 = x; // Load x
}
static void Thread_2()
{
x = 1; // Store x
//-------------------------
Thread.MemoryBarrier();
r1 = y; // Load 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}번만에 빠져나옴!");
}
}
}
메모리 배리어에도 종류가 여러가지가 있다.
1) Full Memory Barrier (ASM MFENCE, C# Thread.MemoryBarrier) : Store/Load 둘 다 막는다.
2) Store Memory Barrier (ASM SFENCE) : Store만 막는다.
3) Load Memory Barrier (ASM LFENCE) : Load만 막는다.