호우동의 개발일지

Today :

article thumbnail

썸네일


사용하는 이유


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

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


1. 코드 재배치 억제

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

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

    • Load Memory Barrier(ASM LFENCE)
      • 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;
        }