[CS] 메모리 배리어

이정석·2023년 7월 25일
0

CS

목록 보기
3/7

메모리 배리어

메모리 배리어는 멀티 프로세서(멀티 쓰레드) 시스템에서 메모리 연산의 순서를 제어하는 방법이다. 메모리 배리어가 필요한 이유는 CPU나 컴파일러가 속도 최적화를 위해 메모리 연산의 순서를 바꿀 수 있기 때문이다.

    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)
    {
        int count = 0;
        while (true)
        {
            count++;
            x = y = r1 = r2 = 0;

            Task t1 = new Task(Thread1);
            Task t2 = new Task(Thread2);
            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            if (r1 == 0 && r2 == 0)
                break;
        }
    }

위 코드에서 메인 쓰레드는 while(true)문을 빠져나올 수 있는지 생각해보자. 위에 있는 4개의 변수(x,y,r1,r2)는 static, 즉 모든 쓰레드가 접근가능한 공유자원이다.

메인 쓰레드는 t1,t2가 끝날때까지 대기하기 때문에 while문 안의 if문에 접근할 때는 t1과 t2의 연산이 끝났음을 확신할 수 있다. 그렇다면 4개의 변수의 초기값은 항상 0이기 때문에 t1과 t2의 연산 순서에 따라 r1,r2값이 달라질 것인데 어떤 순서로 연산을 해도 r1과 r2가 동시에 0일 수는 없다.

하지만, 실행해보면 while문을 빠져나와 정상적으로 프로그램이 종료된다.

1. Thread.MemoryBarrier()

처음에 설명했듯이 CPU나 컴파일러가 속도 최적화를 위해 메모리 연산 순서를 바꿀 수 있다. 위의 예제에서는 아래 내용이 해당되는 부분이다.

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

Thread2로 해당되는 내용이지만 Thread1만 보자면, y=1r1=x는 서로 관계 없는 연산이라고 볼 수 있다. 겹치는 변수가 없기 때문에 컴파일러(CPU)는 두 연산의 순서가 바뀌어도 괜찮다고 생각해 어떤 경우에 두 연산순서를 바꾸어 진행한다.

연산순서가 바뀐 상황에서는 r1과 r2가 동시에 0일 가능성이 충분히 존재한다. x,y가 1이 되기 전에 r1,r2값을 바꿔주면 되기 때문이다.

메모리 배리어를 사용하기 위해서 C#에서는 Thread.MemoryBarrier()를 사용한다. 위 문제를 해결하기 위해서 Thread1Thread2를 다음과 같이 바꾸면 된다.

    static void Thread1()
    {
        y = 1;
        Thread.MemoryBarrier();
        r1 = x;
    }

    static void Thread2()
    {
        x = 1;
        Thread.MemoryBarrier();
        r2 = y;
    }

2. 가시성

메모리 배리어는 가시성문제를 해결할 수 있는데 가시성문제는 한 쓰레드(코어)에서의 변경사항이 다른 쓰레드(코어)에서 바로 보이지 않는 상황을 의미한다.

이런 상황을 잘 나타내는것이 바로 여러 코어를 가지고 있는 CPU이다.

위 그림을 보면 CPU Core는 자신만의 Cache를 가지고 있고 각 코어가 연산한 결과(쓰레드 연산)는 다른 코어의 Cache에 즉시 반영되지 않을 수 있다. 다른 코어에서 연산한 결과를 동기화 시키기 위해 메모리 배리어를 사용할 수 있다.

메모리 배리어는 다른 코어(쓰레드)의 결과를 동기화 시키기 위해 메모리 액세스 순서를 강제한다고 생각하면 된다.

profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글