Home [멀티쓰레드] 컴파일러 최적화
Post
Cancel

[멀티쓰레드] 컴파일러 최적화

컴파일러 최적화(Release 모드)

간단한 멀티쓰레드 코드를 작성해보자.

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
static bool _stop = false; // 전역 변수는 쓰레드 간 공유된다.

static void Thread1()
{
    Console.WriteLine("쓰레드 시작!");

    while(_stop == false)
    {
        // 누군가가 stop 신호를 해주기를 기다린다.
    }

    Console.WriteLine("쓰레드 종료!");
}

static void Main(String[] args)
{
    Task t = new Task(Thread1);
    t.Start();

    Thread.Sleep(1000); // 1초 대기

    _stop = true;

    Console.WriteLine("Stop 호출");
    Console.WriteLine("종료 대기중");

    t.Wait(); // Thread1이 종료될때까지 기다린다.

    Console.WriteLine("종료 성공");
}

정상 실행이 된다면 아래와 같이 결과가 나타날 것이다.(마지막에 종료 성공이 떴다면 정상)

1
2
3
4
5
쓰레드 시작!
쓰레드 종료!
Stop 호출
종료 대기중
종료 성공

그러나 여기서 컴파일을 Debug에서 Release 모드로 변경하여 컴파일을 한다면 어떻게 될까.

Release모드 Release모드 선택 방법

Release 모드로 컴파일했을 경우 실행 결과(상황에 따라 다를 수 있음)

1
2
3
쓰레드 시작!
Stop 호출
종료 대기중

종료 대기중에서 더이상 넘어가지 않는 것을 볼 수 있다.

이러한 현상이 발생하는 이유는 컴파일러 최적화하는 과정에 있다.
Release 모드는 보통 프로그램 개발이 완료되어 최종 배포할 때 사용하는데 컴파일할 때 여러 최적화가 이루어져 프로그램이 빠르게 실행되도록 한다.
그러나 최적화 하는 과정에서 의도했던 것과 다르게 바뀔수도 있다.

위 코드에서 while문 부분에 중단점을 걸어놓고 Release모드 상태에서 디버깅을 돌려보자.(디버깅을 실행했을 때 경고창이 뜬다면 ‘[내 코드]는 사용 안 함 그리고 계속’을 선택해주면 된다.)

Release디버깅 while문에 중단점 지정

디버깅 실행 후 중단점에서 멈추었다면 ‘디버그 -> 창 -> 디스어셈블리’를 눌러보면 어셈블리어로 변형된 코드를 볼 수 있다.

디스어셈블리 디스어셈블리 창

어셈블리어를 모르더라도 while문 부분만 간단하게 해석해보자면

1
2
3
4
5
00007FFC8EEB2D36  movzx       ecx,byte ptr [7FFC8EF3FBF2h]  ; 데이터를 불러와 ecx에 저장

            while(_stop == false)
00007FFC8EEB2D3D  test        ecx,ecx     ; ecx가 0인지 1인지 검사
00007FFC8EEB2D3F  je          serverTest.Program.Thread1()+01Dh (07FFC8EEB2D3Dh)  ; 0이라면 바로 윗 줄로 이동

ecx라는 레지스터에 어떠한 데이터를 꺼내와서 저장하고 ecx가 0인지 1인지 검사한 후 0이라면 다시 바로 윗 줄로 이동하여 반복한다. 결론은 ecx에 0(false)이 들어가있다면 무한 반복하게 되는 것이다.

이해를 위해 의사코드로 나타내보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 최적화하는 과정에서 변형된 코드(의사코드)
if(_stop == false)
{
    while(true)
    {

    }
}

// 처음 작성한 코드
while(_stop == false)
{

}

최적화하는 과정에서 컴파일러는 이 코드가 멀티쓰레드 환경에서 실행된다라는 것을 고려하지 않고, 단순히 현재 코드를 보았을 때 어짜피 while문 안에서는 _stop 값을 변환해주지 않기 때문에 false인 경우 그냥 while(true)로 동작하도록 수정해버린 것이다.

일단 위 문제에 대한 해결은 static bool _stop 앞에 volatile를 붙여주면 된다.
volatile를 붙이면 휘발성이라는 의미로 언제 어떻게든 바뀔 수 있으니 최적화를 하지말라는 의미가 된다.

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
volatile static bool _stop = false;

static void Thread1()
{
    Console.WriteLine("쓰레드 시작!");

    while(_stop == false)
    {
        // 누군가가 stop 신호를 해주기를 기다린다.
    }

    Console.WriteLine("쓰레드 종료!");
}

static void Main(String[] args)
{
    Task t = new Task(Thread1);
    t.Start();

    Thread.Sleep(1000);

    _stop = true;

    Console.WriteLine("Stop 호출");
    Console.WriteLine("종료 대기중");

    t.Wait();

    Console.WriteLine("종료 성공");
}
1
2
3
4
5
쓰레드 시작!
Stop 호출
종료 대기중
쓰레드 종료!
종료 성공

volatile의 경우 다른 의미가 있기도 하지만 사실 잘 사용되지는 않고 보통 메모리 배리어나 lock, atomic과 같은 옵션들을 사용하기 때문에 크게 중요하지 않다.
그보다 중요한 것은 Release 모드에서는 컴파일러 최적화 때문에 일어나지 않던 문제가 발생할 수 있다는 점이다.

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