사용하는 이유
CPU가 코드 재배치
public class Program
{
static int x = 0;
static int y = 0;
static int r1 = 0;
static int r2 = 0;
static void Thread1()
{
y = 1;
r1 = x;
}
static void Thread2()
{
x = 1;
r2 = y;
}
static void Main(string[] args)
{
while (true)
{
x = y = r1 =r2 = 0;
Task task1 = new Task(Thread1);
Task task2 = new Task(Thread2);
task1.Start();
task2.Start();
Task.WaitAll(task1, task2); // 두 쓰레드가 끝날때까지 대기
// r1과 r2이 0일때 반복문 탈출
if (r1 == 0 && r2 == 0)
{
break;
}
}
Console.WriteLine("반복문 탈출");
}
<출력>
- 절차적으로 실행되면 r1 = x, r2 = y에 의해 무한루프가 일어나야 한다.
→ But, 반복문을 탈출한다. - why? CPU가 코드를 재배치하기 때문
- 코드 간의 의존성이 없다고 판단되면 CPU가 임의적으로 순서를 바꿔서 코드를 실행
- r1 = x;를 먼저 실행하고 y = 1을 실행한다.
- r2 = y;를 먼저 실행하고 x = 1을 실행한다.
- 두 스레드는 병행적으로 진행하기에 x = 1, y = 1보다 r1 =x, r2= y가 먼저 실행되어
r1과 r2에 0이 할당될 수 있다.
- 이러한 의도적이지 않은 오류를 메모리 베리어로 방지 가능
- 코드 간의 의존성이 없다고 판단되면 CPU가 임의적으로 순서를 바꿔서 코드를 실행
1. 코드 재배치 억제
- 메모리 베리어 종류
Full Memory Barrier(ASM MFENCE)
- Store/Load 둘 다 막음
- C# →
Thread.MemoryBarrier()
Store Memory Barrier(ASM SFENCE)
- Store만 막는다.
- Store만 막는다.
Load Memory Barrier(ASM LFENCE)
- Load만 막는다.
- Load만 막는다.
namespace ServerCore
{
public class Program
{
static int x = 0;
static int y = 0;
static int r1 = 0;
static int r2 = 0;
static void Thread1()
{
y = 1;
Thread.MemoryBarrier(); // 이 코드를 기점으로 위 아래의 코드가 바뀌지 않음
r1 = x;
}
static void Thread2()
{
x = 1;
Thread.MemoryBarrier();
r2 = y;
}
static void Main(string[] args)
{
while (true)
{
x = y = r1 =r2 = 0;
Task task1 = new Task(Thread1);
Task task2 = new Task(Thread2);
task1.Start();
task2.Start();
Task.WaitAll(task1, task2); // 두 쓰레드가 끝날때까지 대기
// r1과 r2이 0일때 반복문 탈출
if (r1 == 0 && r2 == 0)
{
break;
}
}
Console.WriteLine("반복문 탈출");
}
}
}
<출력>
- 정상적으로 무한루프 발생
Thread.MemoryBarrier()
함수를 사용
2. 가시성
Thread.MemoryBarrier()
를 해준다는 것은 공용 메모리와 동기화 작업을 해주는 것과 같음
static void Thread1()
{
y = 1;
Thread.MemoryBarrier(); // x의 정확한 값을 받기 위해 업데이트(동기화)
r1 = x;
}
static void Thread2()
{
x = 1;
Thread.MemoryBarrier(); // y의 정확한 값을 받기위해 업데이트
r2 = y;
}