Q1. 멀티스레드에서 release 모드로 바꿔서 실행하면 왜 breakpoint에서 안멈춘 걸까?
싱글 스레드에서 잘 돌아가다 멀티 스레드로 오면 버그가 되는 상황 하나씩 알아볼거야.
일단 SeverCore의 코드들을 삭제하고 빈 상태에서 다시 시작해보자.
namespace ServerCore
{
internal class Program
{
static void Main(string[] args)
{
}
}
}
일단 코드를 따라 치면서 구현을 하면 된다.
namespace ServerCore
{
internal class Program
{
static bool _stop = false; // 전역은 모든 스레드들이 동시 접근 가능. 스레드들이 동시 접근할 때 어떤 일이 일어날지 살펴본다.
static void ThreadMain()
{
Console.WriteLine("쓰레드 시작!");
while(_stop == false)
{
// 누군가가 stop 신호를 해주기를 기다린다
}
Console.WriteLine("쓰레드 종료!");
}
static void Main(string[] args)
{
Task t = new Task(ThreadMain);
t.Start();
Thread.Sleep(1000); // 1초 동안 잠들었다 다시 깨는 함수
_stop = true;
Console.WriteLine("Stop 호출");
Console.WriteLine("종료 대기중");
t.Wait(); // 스레드가 끝났는지 알아 보는 함수. Thread일 때는 Join, Task는 Wait
Console.WriteLine("종료 성공");
}
}
}
이게 잘 되는지 ctrl+f5를 눌러서 실행 해보자.
아무런 문제 없다.
한번에 두개의 영혼이 병렬로 실행된 느낌.
전역으로 된 변수는 ThreadMain도 안에서 접근 할 수 있었고, Main스레드도 접근할 수 있었다. 까지 테스트를 해봤어.
지금까진 Debug모드였어. 게임 배포할 때는 Release모드로 바꾸게 된다. 온갖 최적화 들어가서 훨씬 빨라지게 된다. 최적화 한다는건 디버깅 어려워진다는 말이 된다.
Release로 해놓고 실행을 해보자.
아까와 다르게 종료 대기중에서 멈추지 않고 있는 걸 볼 수 있다.
무한 루프 돌고 있는데 어떤 최적화 했길래 그런걸까.
멀티스레드에서 비일비재한다.
realese에선 breakpoint 잘 안먹는다고 하지만 그래도 ThreadMain의 while(_stop == false)에 잡아보자.
실행하고 경고 떠도 continue한다.
내 컴에선 breakpoint 안잡히지만 강의 내용을 보면 잡히는데
디버그→창→디스어셈블리 누르면 어셈블리 언어 나온다. (컴퓨터에 가까운 언어)
메모리 끄집어 와서 ecx라는 레지스터에 넣어 줬는데 ecx가 0이 아니면 jump equal 즉 다시 test ecx, ecx로 돌아가겠다는 내용.
다시 코드로 가서 의사 코드로 표현하면
if(_stop == false)
{
while(true)
{
}
}
기계어로 번역이 되면서 주인이 코드를 멍청하게 짰다고 판단
while(_stop == false)
{
// 누군가가 stop 신호를 해주기를 기다린다
}
여기 안에서 _stop을 바꿔주는 코드가 없기 때문에 의사코드처럼 바꿔줘도 로직이 똑같이 동작한다. (멀티 스레드임을 상관 안한다면)
그래서 release 모드에서는 이런 느낌으로 바뀐 것이다.
우리를 위해서 바꿔줬지만 최적화 해준 악수가 된거.
최적화 안하게 강제하는 방법은 여러가지 있는데 간단한 방법
volatile static bool _stop = false;
앞에 volatile을 붙이는 거. 휘발성이란 의미. _stop은 휘발성이니까 얼씬도 하지 말아라. 최적화 하지 말아라. 라는 의미.
다시 실행해 보면
이제 정상적으로 된다.
volatile 키워드는 C++에도 C#에도 있긴 한데 C#에선 의미가 좀 다르다.
C++에서도 코드상에서 최적화 하지 말라는 의미 있지만 C#에서는 플러스로 cache를 무시하고 _stop의 최신값을 가져와라 라는 의미도 있다. 이걸 이해하려면 cache의 개념도 이해해야 하고 복잡해 진다. 심지어 이것 뿐만 아니라 c# 에서 특이하고 이상하게 동작하기 때문에 c#전문가가 기고한 글을 보면 잊고 쓰지 말라 추천한다. 나중에 memory barrier, lock, atomic 같은 다른 옵션을 쓰라고 하기 때문에 얘는 오늘만 이런 애가 있었구나 하고 잊으면 된다.
오늘 중요했던 건 volatile을 이용해서 고쳤다는 게 중요한게 아니라 release 모드에서는 코드 최적화 때문에 일어날 수 있는 버그가 일어날 수 있다가 핵심이다.
'Server programming' 카테고리의 다른 글
01_05_멀티쓰레드_메모리 베리어 (0) | 2023.03.13 |
---|---|
01_04_멀티쓰레드_캐시이론 (0) | 2023.03.12 |
01_02_멀티쓰레드_쓰레드 생성 (0) | 2023.03.12 |
01_02_멀티쓰레드_쓰레드 생성(구버전) (0) | 2023.03.12 |
01_01_멀티쓰레드 개론 (0) | 2023.03.12 |
댓글