호우동의 개발일지

Today :

article thumbnail

썸네일


1. 사용하는 이유


1.1. CPU가 코드 재배치

<code />
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이 할당될 수 있다.

    • 이러한 의도적이지 않은 오류를 메모리 베리어로 방지 가능


1.2. 1. 코드 재배치 억제

  • 메모리 베리어 종류
    • Full Memory Barrier(ASM MFENCE)
      • Store/Load 둘 다 막음
      • C# → Thread.MemoryBarrier()

    • Store Memory Barrier(ASM SFENCE)
      • Store만 막는다.

    • Load Memory Barrier(ASM LFENCE)
      • Load만 막는다.

<code />
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() 함수를 사용

 


1.3. 2. 가시성

  • Thread.MemoryBarrier()를 해준다는 것은 공용 메모리와 동기화 작업을 해주는 것과 같음
<code />
static void Thread1() { y = 1; Thread.MemoryBarrier(); // x의 정확한 값을 받기 위해 업데이트(동기화) r1 = x; } static void Thread2() { x = 1; Thread.MemoryBarrier(); // y의 정확한 값을 받기위해 업데이트 r2 = y; }