Home [멀티쓰레드] 메모리 배리어
Post
Cancel

[멀티쓰레드] 메모리 배리어

메모리 배리어

메모리 배리어(Memory Barrier)는 하드웨어(CPU)나 컴파일러에게 특정 연산의 순서를 강제하도록 하는 기능이다.

하드웨어(CPU)나 컴파일러에서는 최적화를 위해 연산 결과에 영향이 가지 않을 정도로 연산의 순서를 뒤바꿀수 있는데 멀티쓰레드 환경에서는 이러한 최적화로 인해 문제가 발생할 수 있다.

하드웨어(CPU) 최적화 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static int x = 0;
static int y = 0;
static int r1 = 0;
static int r2 = 0;

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

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

    Console.WriteLine($"{count}번만에 빠져나옴!");
}

위 코드를 보면 Thread_1과 Thread_2를 수행한 후 r1과 r2가 0인 경우에만 while문을 빠져나올 수 있다. 그러나 코드를 좀만 더 자세히 살펴본다면 r1과 r2가 동시에 0일 수가 없기 때문에 while문을 빠져나오지 못해야 정상이다.

하지만 실행시켜보면 의외로 금방 빠져나오는 것을 볼 수 있다.

1
23번만에 빠져나옴!

while문을 빠져나오지 못해야 정상이지만 이러한 현상이 발생하는 이유는 하드웨어(CPU)에서는 연산 처리를 효율적으로 하기 위해 y = 1;과 r1 = x;의 순서를 뒤바꿀 수 있기 때문이다.(Thread_2도 마찬가지)

싱글쓰레드에서는 y = 1;과 r1 = x;의 순서를 뒤바꿔도 결과에는 아무런 영향이 없기 때문에 최적화를 위해서 바뀌어도 상관없지만 멀티쓰레드 환경에서는 위와 같은 문제가 발생할 수 있다.

이러한 문제를 방지하기 위해 메모리 배리어를 사용할 수 있다.

메모리 배리어 사용법

메모리 배리어는 아래와 같이 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Thread_1()
{
    y = 1;  // Store y
    Thread.MemoryBarrier(); // 메모리 배리어
    r1 = x; // Load x
}

static void Thread_2()
{
    x = 1;  // Store x
    Thread.MemoryBarrier(); // 메모리 배리어
    r2 = y; // Load y
}

위와 같이 메모리 배리어를 사용하면 y = 1;은 메모리 배리어 아래로 넘어가지 못하고 r1 = x;는 메모리 배리어 위로 넘어가지 못한다. 이로 인해 두 코드의 순서를 강제할 수 있게 된다. (Thread_2도 마찬가지)

메모리 배리어를 사용하여 아까 코드를 실행시켜보면 while문을 빠져나오지 못하는 것을 볼 수 있다.

메모리 배리어 종류

  • Full Memory Barrier - Store와 Load 둘다 막는다.
    • 어셈블리어 - MFENCE
    • C# - Thread.MemoryBarrier()
  • Store Memory Barrier - Store만 막는다.
    • 어셈블리어 - SFENCE
  • Load Memory Barrier - Load만 막는다.
    • 어셈블리어 - LFENCE

가시성

메모리 배리어는 코드 순서를 강제하는 용도로도 사용할 수 있지만 가시성을 위해서도 사용할 수 있다.

CPU에는 코어가 여러개 있고 각 코어들마다 캐시를 들고 있기에 이론상 동일한 변수라고 해도 코어들마다 다른 값을 가지고 있을 수 있다. 그렇기에 값이 바뀌면 메모리에 갱신하거나 최신 값을 불러와야하는데 메모리 배리어가 이러한 용도로도 사용할 수 있다.(요즘 CPU에서는 이러한 문제가 거의 발생하지 않는다.)

메모리 배리어를 직접적으로 사용할 일은 거의 없겠지만 간접적으로 사용될 경우는 많기 때문에 원리라도 잘 이해하고 있는 것이 좋다.

This post is licensed under CC BY 4.0 by the author.