본문 바로가기
게임개발/게임서버

메모리 배리어

by do_ng 2024. 4. 18.

1. 메모리 배리어 정의 
중앙처리장치(CPU)나 컴파일러에게 특정 연산의 순서를 강제하는 기능 

 

2. 메모리 배리어가 없을시의 문제점 

중앙처리장치와 컴파일러는 멀티스레드 환경에서 개발자가 의도한 로직 순서를 뒤바꿀 수도 있는데 로직 실행 순서가 바뀌어 실행된다면 잘못된 결과가 발생할 수 있다. 

 

3. 메모리 배리어의 용도 

- 코드 재배치 억제 

int a = 0;

void Thread_A() {
	a = 10;
   	console.writeLine(a);
}

void Thread_A() {
	a = 10;
    	Thread.MemoryBarrier();
   	console.writeLine(a);
}

멀티 스레드 환경에서 메모리 배리어가 없으면 무조건 "a=10" 라는 연산이 처음부터 실행되는 게 아니라 

"console.writeLine(a)" 연산이 먼저 실행될 수 있음 이런 식으로 코드의 순서가 바뀌기 때문에 메모리 배리어를 사용해서 코드가 재배치되는 것을 억제한다.

 

- 가시성의 역할 수행(동기화 작업)

: 멀티스레드 환경에서 각각의 스레드가 공유자원에 대해서 모두 같은 상태를 바라보고 있는 것 (동기화 작업을 수행) 

마찬가지로 DB에서 각각의 개발자가 로컬에서 데이터를 Insert, delete 하고 Commit 때려야지 모든 개발자가 공유하는 작업공간에 실제 반영이 돼서 데이터가 일치하는 현상과 유사하다. 

 

마치 식당에서 주문을 했는데 기존에 주문을 시킨 종업원이 아닌 새로운 종업원에게 주문을 변경할 때

그 종업원이 최신 정보를 가지고 있지 않는 경우에 데이터 불일치가 발생하기 때문에 멀티 스레드 환경에서는 데이터를 일치시키는 역할이 중요하다. 

 

4. 코드

 

- 메모리 배리어를 사용하지 않은 경우

using System;
using System.Threading;


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

        static void Thread_1() {
            y = 1;
            r1 = x;
        }

        static void Thread_2() {
            x = 1;
            r2 = 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;
            }

            // Q) 몇번만에 빠져나오는가? 
            Console.WriteLine($"{count} 번만에 빠져나옴");
        }
    }
}

 

static void Thread_1() {
    y = 1;
    r1 = x;
}

 

CPU 입장에서 Thread의 작업을 실행할 때 Thread 내의 어떤 명령을 먼저 실행 할지 딱히 정해져 있지 않으면 개발자가 의도한 로직에 맞지 않게 실행된다.

개발자가 의도한 순서는 "y = 1"  -> "r1 = x"이지만,  "r1 = x" -> "y = 1" 명령이 실행될 수 있다.

 

 

- 메모리 배리어를 사용한 경우

using System;
using System.Threading;


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

        static void Thread_1() {
            y = 1;
            // 저장한 데이터를 공유자원에 최신화시킴 
            // 데이터를 최신화하고 road 해서 가져옴
            Thread.MemoryBarrier(); 
            r1 = x;
        }

        static void Thread_2() {
            x = 1;
            // 저장한 데이터를 공유자원에 최신화시킴 
            // 데이터를 최신화하고 road 해서 가져옴
            Thread.MemoryBarrier();
            r2 = 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;
            }

            // Q) 몇번만에 빠져나오는가? 
            Console.WriteLine($"{count} 번만에 빠져나옴");
        }
    }
}

 

'게임개발 > 게임서버' 카테고리의 다른 글

동기화  (2) 2024.04.18
멀티스레드 InterLocked  (1) 2024.04.18
멀티 쓰레드 프로그래밍  (0) 2024.04.17
멀티쓰레드 Lock 구현 방식  (2) 2024.01.15
소켓 프로그래밍  (0) 2023.10.29