지난시간에 쓰레드를 생성하고 간단하게 사용하는 것 까지 해보았다.
그런데 이것이 끝이 아니다.
오히려 반대로 이제 멀티쓰레드의 진짜 시작이다.
왜냐하면 우리가 지금 까지 만들었던 코드들은 싱글 쓰레드기반으로 돌고 있었었다.
사실은 아무런 문제 없이 돌아가던 것들이
멀티쓰레드 세상이 되면 버그가 되고 아주 난리가 난다.
그래서 오늘은 그런 상황들에 대해서 하나하나씩 살펴보도록 할 것이다.
그래서 오늘은 servercore에서 작업하던 것들 다 날려주고
이상태에서 작업을 해보도록 하겠다.
오늘은 그래서 이상태에서 코드를 따라 치면서 구현을 해보면 될 것이다.
그래서 이렇게 _stop이라는 boolean변수를 하나 만들어 주는데
우리가 이제 쓰레드를 사용을 할 때,
스택 메모리는 각자의 스택 메모리를 할당 받아가지고 사용을 한다고 했었다!
그런데 _stop처럼
이렇게 전역으로 선언된 변수들은
모든 쓰레드들이 공통으로 사용해서 동시에 접근을 할 수 있었다고 했다!.
멀티쓰레드 개요에서
이렇게 설명을 해주었었다.
그래서 오늘은 동시에 접근을 하면 어떤 일이 일어나는지 테스트를 해보도록 하자.
그래서 쓰레드의 메인인 쓰레드 메인을 만들어주자
이녀석이 하는 일은 굉장히 간단한데
이렇게 외쳐주고
그다음에
이런식으로 _stop이 false일동안 무한 루프를 돌 것이다.
이제 이것이 ThreadMain이 하는 그런 동작이된다.
그러면 이제
MainThread(메인쓰레드)에서는 쓰레드를 만들어 주어야한다.
쓰레드는 Task로 만들어 주도록 하자.
그래서 Task t = new Task에다가 ThreadMain을 넣어 주면 되고
t.Start()를 하게되면 쓰레드가 ThreadPool에 있던
쓰레드를 이용을 해가지고
일감 하나를 분배를 받아서 뿅하고 실행을 하는 상태가 될 것이다.
그리고
Thread.Sleep(1000)을하게되면 ( ) 인자가 ms단위여서 1초동안 sleep하겠다는 의미인데
그렇다면 이 Main Thread는 1초동안 잠시 잠들었다가
다시 깨는 그런 함수가 될 것이다.
이녀석은 그냥 1초동안 대기 하고싶을 때 넣어주는 것이다.
그리고 1초 동안 대기하다가 깻으면
_stop을 true로 바꾸어 줄 것이다.
그래서 ThreadMain이 실행될 충분한 시간을 만들어 준다음에
1초후에
_stop을 true로 만들어 주어서 while문을 빠져 나오도록 하는 것이다.
그래서 이런식으로 될텐데
그전에 Task t가 끝난 것을 확인을 하고 싶은데
그것은
t.Wait라는 것으로 알 수가 있다.
Thread일 경우에는 t.Join이라는 녀석 이였는데(Thread의 일감이 다 끝날때 까지 기다려 주는것)
Task는 Wait이라는 그런 함수가 있다.
이제 여기서 어떤 느낌이냐 하면은
Main에서 Thread를 생성을 해가지고
ThreadMain을 실행을 하도록 만들어 준것이고
여기서 Sleep을 한다음에
멈춰도 된다고 시그널을 주었으니까
반대쪽에서 실행되고있던 녀석은 뺑뺑이를 돌고있다가
_stop == true로 바꿔 줬으니까 빠져나와가지고
밑에 콘솔롸이트 실행하게된다.
이것을 테스트를 해보면은
지금 쓰레드 시작! 하고
while문 계속 돌다가
_stop이 true로 바뀌고
while문 빠져나온다음에
밑에 cw tabtab이 순차적으로 실행되는 것을 볼 수 있다.
그래서 이렇게 되니까
한번에 두개의 영혼이
병렬로 실행된 그런 느낌이다.
그래서 이렇게 전역으로 선언 한 변수는
이렇게 ThreadMain에서도 접근을 할 수 있었고
Main에서도 이렇게 접근을 할 수 있었다.
그런데 이제 문제가 하나씩 하나씩 들어나기 시작할 것입니다.
"이게 과연 괜찮을까??"
우리가 이때 까지는
여기 이 Debug 모드라는 것으로 만들고 있었었다.
그런데 이제 실제로 나중에 게임을 배포를 하고 라이브에 나가게 되면은
Debug가 아니라
여기서 두번째
release모드로 바꾸게된다.
release Mode로 바꾸게 되면은 온갖 최적화가 들어가게 되가지고
프로그램이 훨씬더 빨라지게 된다.
그리고 그렇게 최적화를 많이 한다는 것은
우리가 프로그래밍을 할 때 디버깅을 하기가 조금더 어려워 진다는 말이 된다.
< 잠깐 모르는것 질문 >
그런데 Debug, 디버깅이 뭔말이냐??
예를 들어
이런곳에 break Point를 잡았는데 안걸리게 되거나 그런 문제가 종종 생기게 될 것이다.
모드를 release로 해놓고 실행을 해보면
Debug모드와는 다르게
종료대기중에서 멈춰지지가 않는 모습을 볼 수 있다.
여기까지는 실행을 했는데
t.Wait에서 돌아오지 않는 다는 뜻인데
여기서 무한 루프를 돌고 있다는 얘기이다.
코드는 건든 것이 없는데 release모드로 바꾸고
도대체 어떤 최적화를 했길래 Debug모드와 차이가 나는 것일까??
그런데 이렇게 멀티 쓰레드 작업을 할때는 이런일이
비일비제하게 일어난다.
분명히 잘 되던게
라이브에서 실제로 게임을 내면은
버그가 터지기 시작한다거나
뭐 이런 끔찍한 상황들이 많이 일어 나가지고
그래서 멀티쓰레드 프로그래밍이 약간 프로그래밍의 흑마법이라고 생각하면 될 것이다.
그런데 이것이 막 랜덤으로 버그가 터지고 이런것은 아닐 것이다.
뭔가가 문제가 있으니까 이런 문제가 발생을 했을 텐데
그러면 일단 breakpoint를 잡아 보도록 하자
이렇게
그런데 아까 말했던것 처럼 release모드에서는 breakPoint가 잘 안먹는다고 했었는데
그냥 이렇게 해서 실행을 해보도록 하겠다.
경우에 따라서 경고창이 떴을 수도 있을 것이다.
그래서 일단
이렇게 breakPoint에서 잡혔는데
그리고
메뉴칸에서 디버그 > 창 > 디스어셈블리 라는 것이 있는데 눌러보면
그러면 난생 처음보는 이런 이상한 화면이 나오는데
그러면 이렇게 외계어가 튀어오는데
이것이 어셈블리 언어 라는 것이다.
어셈블리 언어가 가장 프로그래밍? 컴퓨터와 가까운 언어라고 보면 된다.
실제로 프로그램이 어떤식으로 실행되는 지를 엿볼 수가 있는데
대학에 다닌다면 조금은 배우겠지만(나는 모름)
지금은 이해를 못해도 괜찮다.
일단 간단하게만
이 줄을 보면은
뭘 하고 있는 것이냐면
이 위에줄에 밑줄 친 부분에서 뭔가 메모리를 끄집어 와서
ecx라는 레지스터에다가 뭔가를 넣어 주었는데
그다음에 ecx안에 있는 레지스터가 0인지 아닌지 체크를 해가지고
jump equal
ServerCore.Program.ThreadMainI()+01Dh (07FFB30802D3Dh)라는 주소로
돌아가서
이 주소로 또다시 돌아가서
또다시 이렇게 실행을 하겠다는 말이다.
강의랑 코드가 살짝 달라서 주소값이 조금 다르게 나오는거 같다
현재 지금 false라는 값이 0인데
test라는 것이 0이 되면은 뺑뺑이를 돌겠다라는 말이 되니까
한마디로 지금
( 어셈블리 종료를 하고 다시 돌아와서 )
이것을 쉽게 얘기를 하자면은
_stop이 false일때 while문을 만들어서 뺑뺑이를 돌도록 코드가 만들어져 있는 것이다.
왜냐햐면은
지금 컴파일러 입장(그러니까) == 지금 우리가
이런 프로그램을 만든다음에
이것이 기계어로 변형이 되면서
어떤일이 발생을 한 것이냐하면은
우리를 위해서(주인님을 위해서) 최적화를 하면 기뻐할거 같으니까
여기있는 코드를 함 본것이다 일단.
그런데 코드를 보다 보니까
주인님이 코드를 좀 멍청하게 짯네?
이렇게 보는 거죠
왜냐하면 이부분에서 이녀석은 "멀티 쓰레드"라는 개념은 이해를 안하고
그냥 사진의 함수만 딱 봤을 때,
_stop이 false라고 했는데
이 while문 안에서 stop을 딱히 바꿔주는 것이 없다는 것은...
지금 이 _stop을
아까와처럼 if문으로 똑같이 빼놓아도 똑같이 동작한다는 뜻이 될 것이다.
그래서 코드가 release 모드에서는 약간
이런 느낌으로 바뀐 것이다.
그래서 우리를 위해서 굉장히 신나게 이렇게 바꾸어 주었는데
결국에는 이렇게 최적화를 해준것이 악수가 된것이다.
우리는 사실 이것을 멀티 쓰레드 환경에서 실행을 하고 있었으니까
다른애가 이런식으로 몰래 접근을 해서 _stop을 true로 바꿔주고 있었기 때문에
이녀석은 결국 최적화를 하면 안된다는 의미가 되는 것이다.
씨발 이해가 안간다 22.02.03 22.30
다시 듣자 다시 보고
22.02.04 00:04
다시 정리를 잠깐 하자면
쓰레드라는 프로그램을 실행을 해주는(직원)이 있는데
직원이 많으면 멀티 쓰레드 이다.
내 컴퓨터는 코어가 8개짜리라서 직원이 8명까지 배치를 해줘야 가장 효율적인데
지금 우리는 멀티 쓰레드 환경이라
각 쓰레드는 static변수는 공통으로 사용을 할 수있다.(접근도 가능함)
스택부분만을 각자의 것으로 관리를 하는데
_stop을 공통으로 접근이 가능하게 되어있는데
모드를 release로 해버리면 컴퓨터가 주인님에게 최적화를 해주기 위해서
주인님이 멍청하게 while문 안에다가 _stop을 빠져나갈 부분을 안 만들어 주었네? 하고 -> if(true) 로 해서 while(true)로 해주었었다.
그래서 이부분을 최적화를 하면 안된다는 말 같다.
그래서 최적화를 안하도록 강제하는 방법이 여러가지가 있는데
가장 간단한 방법은
여기 앞에다가
volatile를 붙이는 것이다.
volatile == "휘발성"이라는 단어이다.
이렇게 붙이게 되면은
이 _stop이라는 녀석은
휘발성 데이터, 즉 언제언제 변할지 모르니까
얘는 얼씬도 하지 말아라, 최적화 하지 말아라
"무조건 있는 그대로 갖다 써라" 그런 얘기가 된다.
이렇게 바꾸어가지고 다시한번 실행을 해보면
이렇게 하면 이제 정상적으로 종료가 되는 것을 볼 수 있다.
그리고 참고로
"volatile" 라는 키워드는 c# 에도 있고 c++에도 있기는 한데
c++에서 "volatile"라는 키워드는 의미가 조금 다르다.
이게 그래서 사람들에게 혼동을 주는 그런 문법이기도 한데
c++에서도 똑같이
지금과 마찬가지로
_stop을 코드상에서 최적화를 하지 말아 달라는 말이 있기는 하다.
그런데 c#에서는 거기서 플러스로
"캐시"를 무시하고
이녀석의 최신 값을 가져와라라는 의미도 있기는 한데
이것을 이해를 하려면 사실 "캐시"에 대한 개념도 이해를 해야되고
굉장히 복잡해 집니다.
그리고 심지어 그것이 전부가 아니다.
이녀석은 정말 c#에서 특이하고 이상하게 동작하기 때문에
이런 c#전문가들이 기고한 글들을 보면은
얘는 그냥 잊고 쓰지 말라고 추천을 한다
그리고 우리가 나중에 배울
"메모리 베리어" 라든가 "Lock"이라든가 "아토믹" 같은
다른 옵션을 쓰라고 하기 때문에
오늘은 그냥 이런애가 있었구나 하고 읽씹하면 될것이다.
그래서 오늘 중요했던것은
"volatile"를 사용해서 뭔가를 고친것이 중요했던것이 아니라
이런식으로 release모드에서는 코드 최적화 떄문에
일어나지 않던 버그들이
일어 날 수도 있는 것이 "핵심"적인 내용이였다.