📄 메모리 배리어

메모리 배리어는

[코드 재배치 억제] 캐시에 의한 재배치, CPU 파이프라인에 의한 코드 재배치에 의해 멀티스레드환경에서 의도하지 않은 연산 결과를 억제할 수 있습니다.

[가시성 보장] 또한 특정 스레드에서 공유 메모리 자원에 대한 값을 변경하고, 다른 스레드가 같은 공유 메모리에 접근할 때, 특정스레드가 변경해놓은 값을 읽어올 수 있도록 합니다.

 

📑 예제

namespace ServerCore
{
    class Program
    {
        static int x = 0;
        static int y = 0;
        static int r1 = 0;
        static int r2 = 0;

        static void Thread1()
        {
            y = 1; //Store y

            //Thread.MemoryBarrier();

            r1 = x; //Load x
        }

        static void Thread2()
        {
            x = 1; //Store x

            //Thread.MemoryBarrier();

            r2 = y; //Load y
        }

        static void Main(string[] args)
        {
            //총 9번의 테스트 진행
            for(int i = 1; i <= 9; ++i)
            {
                int count = 0;
                while (true)
                {
                    ++count;

                    x = y = r1 = r2 = 0;

                    Task task1 = new Task(Thread1);
                    Task task2 = new Task(Thread2);

                    task1.Start();
                    task2.Start();

                    //task1, task2가 끝날때까지 기다린다.
                    Task.WaitAll(task1, task2);

                    //이 코드는 이론적으로 task1, task2가 끝나야 실행된다
                    //r1, r2가 둘다 0이 될 수 없는 조건
                    if (r1 == 0 && r2 == 0) { break; }
                }

                Console.WriteLine($"{i}번째 케이스의 시도횟수는 {count}번");
            }
        }
    }
}

이상하게도 break조건문이 참이되어 프로그램이 종료되었다.

  • 각 스레드에서 값을 저장하고, 읽어오는 과정에서 break 조건문이 무조건 거짓이 나오도록 하지만, 놀랍게도 이 테스트케이스는 break가 참이 되며, 빠져나오는 것을 볼 수 있습니다.
  • 각 케이스마다 실행되는 횟수가 모두 다른것을 볼 수 있습니다.

 

Thread.MemoryBarrier(); //메모리 배리어 사용
  • 메모리 배리어를 사용하면 컴파일러 최적화처럼 CPU파이프라인 및 캐시에 의한 코드 재배치로 인해 스레드 내의 연산 순서가 바뀔 수 있는 경우를 억제합니다.

 

📑 메모리 배리어

namespace ServerCore
{
    class Program
    {
        static int x = 0;
        static int y = 0;
        static int r1 = 0;
        static int r2 = 0;

        static void Thread1()
        {
            y = 1; //Store y

            Thread.MemoryBarrier();

            r1 = x; //Load x
        }

        static void Thread2()
        {
            x = 1; //Store x

            Thread.MemoryBarrier();

            r2 = y; //Load y
        }

        static void Main(string[] args)
        {
            //총 9번의 테스트 진행
            for(int i = 1; i <= 9; ++i)
            {
                int count = 0;
                while (true)
                {
                    ++count;

                    x = y = r1 = r2 = 0;

                    Task task1 = new Task(Thread1);
                    Task task2 = new Task(Thread2);

                    task1.Start();
                    task2.Start();

                    //task1, task2가 끝날때까지 기다린다.
                    Task.WaitAll(task1, task2);

                    //이 코드는 이론적으로 task1, task2가 끝나야 실행된다
                    //r1, r2가 둘다 0이 될 수 없는 조건
                    if (r1 == 0 && r2 == 0) { break; }
                }

                Console.WriteLine($"{i}번째 케이스의 시도횟수는 {count}번");
            }
        }
    }
}

break 조건문이 항상 거짓이기에 아무것도 출력되지 않는다.

  • 메모리 배리어를 사용하여 연산 순서가 변경되는 코드재배치를 방지하고, 가시성을 보장하여 코드 상에서 보인 break조건문에 대해 항상 거짓이 나오도록 되었습니다.
  • 실행 결과(count 출력) 또한 나오지 않은것을 확인할 수 있습니다.

'server > socket server' 카테고리의 다른 글

[C# 서버] Context Switching  (0) 2023.01.04
[C# 서버] 스핀락  (0) 2023.01.04
[C# 서버] 원자성, Interlocked, Monitor, lock  (0) 2023.01.02
[C# 서버] volatile  (0) 2022.12.30
[C# 서버] 멀티스레드(Multi-thread) 기초  (0) 2022.12.30
bonnate