이번 포스트에서는 컴파일러 최적화에 대해 알아보겠습니다.
일단 다음의 코드를 살펴보겠습니다.
class Program
{
static bool _stop = false;
static void AddedThread()
{
Console.WriteLine("추가한 스레드 시작!!!");
while (_stop == false)
{
}
Console.WriteLine("추가한 스레드 종료!!!");
}
static void Main(string[] args)
{
Task t = new Task(AddedThread);
t.Start();
Thread.Sleep(1000);
_stop = true;
Console.WriteLine("_stop 변경");
Console.WriteLine("main 스레드 종료 대기중");
t.Wait();
Console.WriteLine("main 스레드 종료 성공");
}
}
지역변수의 경우 각기 다른 스레드들의 Stack에 저장되어 사용하지만,
전역변수의 경우 Data 영역에 보관되어 모든 스레드들이 같이 사용합니다.
따라서 위의 _stop은 모든 스레드들이 같이 사용합니다.
Task를 새로 추가합니다.
이때 추가한 Task는 AddedThread를 수행합니다.
AddedThread의 경우 두 개의 출력문과 하나의 반복문을 같고 있습니다.
이 반복문은 _stop이 true가 되면 수행하지 않게끔 설계되었습니다.
추가된 Task를 Start 하여 실행한 이후에 main 스레드에 sleep 함수를 걸어
1초 뒤에 다시 동작하게끔 설계하였습니다.
1초 뒤에 _stop을 true로 바꾸고 main 스레드에서 출력문을 출력합니다.
이때 Task에 Wait을 걸어 Task를 묶어두겠습니다.
실행결과는 다음과 같습니다.
Task의 Start()에서 '추가한 스레드 시작!!!' 을 출력합니다
그 후에 main스레드가 잠들어있는 1초동안 무한반복문을 수행하다가 main스레드에서 _stop을 변경합니다.
따라서 Task에서는 무한반복문을 종료하고 '추가한 스레드 종료!!!' 를 출력합니다.
main 스레드에서는 '_stop 변경' 을 출력하고,
이어서 'main 스레드 종료 대기중' 을 출력하고 최종적으로 Task가 종료되었으므로
'main 스레드 종료 성공' 을 출력하게 됩니다.
이는 기존의 멀티스레드 방식과 크게 다르지 않은 수행과정입니다.
하지만 상단의 Solution Configuration을 Debug에서 Release로 변경하면
다음과 같은 문제가 발생합니다.
출력된 문장의 순서가 기존과 다릅니다.
Task가 Start 한것까지는 괜찮지만, main 스레드가 잠들어있는 동안 바뀌지 말아야 할 _stop을
누군가가 바꿔버렸고 main 스레드의 로직을 수행하였습니다.
결국 _stop의 값이 변경되어 무한반복문을 탈출하고 즉시 '추가한 스레드 종료!!' 가 출력되었습니다.
그 후에는 main 스레드에서 'main 스레드 종료 성공' 을 출력하게 됩니다.
이는 Configuration이 Release 모드로 설정되었기에 발생하는 문제입니다.
Release 모드는 Debug 모드와 달리 만든 앱을 배포하는 경우에 사용합니다.
따라서 디버그와 같은 기능을 매우 제한하고 최적화할 수 있는 모든 것을 최적화합니다.
이때 _stop의 경우도 컴파일러에 의해 최적화되어 _stop의 값이 바뀌게 된 것입니다.
따라서 이 문제를 해결하기 위해서는 _stop이 최적화의 대상이 되지 않도록 수정해야 합니다.
1) 코드를 수정
최적화를 방지하는 방법은 여러가지가 존재합니다.
추후에 배워볼 MemoryBarreier나 Atomic 키워드, lock 키워드 등이 있지만,
이번에는 volatile 키워드를 사용하여 최적화를 방지해보겠습니다.
volatile 키워드는 모호함과 성능 문제로 인해 사용이 추천되지는 않습니다.
수정 코드는 다음과 같습니다.
class Program
{
volatile static bool _stop = false;
static void AddedThread()
{
Console.WriteLine("추가한 스레드 시작!!!");
while (_stop == false)
{
}
Console.WriteLine("추가한 스레드 종료!!!");
}
static void Main(string[] args)
{
Task t = new Task(AddedThread);
t.Start();
Thread.Sleep(1000);
_stop = true;
Console.WriteLine("_stop 변경");
Console.WriteLine("main 스레드 종료 대기중");
t.Wait();
Console.WriteLine("main 스레드 종료 성공");
}
}
_stop에 volatile 키워드를 수식하여 컴파일러의 최적화 대상에서 지웠습니다.
실행 결과는 다음과 같습니다.
'C#' 카테고리의 다른 글
Memory Barrier (0) | 2024.11.27 |
---|---|
캐싱 (1) | 2024.11.27 |
C#에서 쓰레드를 생성하는 방법 (0) | 2024.11.25 |
람다식 (0) | 2024.09.28 |
이벤트 키워드 (0) | 2024.09.14 |