지난 시간까지
관리자 직원(Kernel Mode)를 거쳐서
락을 구현하는 세가지 방법에 대해서 배웠었다.
AutoResetEvent
MaualResetEvent
Mutex
이렇게 세가지
이번에도 새로운 락에대해서 배울 것인데
배우기 전에 복습을 잠깐하고 가도록 하자.
그래서 보면은
락을 구현을 할 때는 세가지 방법이 있다고 했었다.
근성 == SpinLock
양보 == Sleep(1), Sleep(0), Yield()
갑질 == AutoResetEvent, ManualResetEvent, Mutex
그리고 이 세가지중 어떤게 더 좋다 이런 개념은 없다.
하나의 장점이 다른 아이의 단점이 되기도하고
이렇게 장단점이 크로스로 왔다갔다 하기 때문에
그리고 심지어 우리가 락을 사용을 할 때
한가지만 골라서 사용하지는 않는다.
애당초 라이브러리 안에서 여러가지 방법을 혼합해서 사용하는 경우가 많다.
그래서 이전에 배웠던 방법중 하나가
이런식으로 object를 하나 만든다음에
사용을 할 때는
이렇게 lock키워드를 사용을 하는 방법이 하나가 있었고
static object _lock = new object();
이녀석은 내부적으로는 Monitor라는 클래스를 이용한다고 했었다.
이전까지는 우리가 직접 구현을 했는데 이렇게 클래스가 있으니 이녀석을 사용을 하면된다.
그래서 굳이 안만들고 이녀석 사용하면된다.
그래서 만들어져 있는 SpinLock은 어떻게 사용을 하는지 잠깐 보면은
Enter, Exit이렇게 있는데
Enter에 빨간줄을 보면은
그래서 _lcokTaken을 false로 하고 인자로 넣어주게 되면은
Enter의 결과값을 _lockTaken에다가 넣어주게 되는데
이렇게 하는 이유는
_lock2.Enter(ref _lockTaken);
이렇게 Enter하는 도중에
exception이 발생을 해가지고(예외적인 상황이 발생을 해가지고)
이부분이 정상적으로 처리가 안되었을 경우 처리를 하기 위해서
_lockTaken의 결과 값을 try위의 _lockTaken에다가 넣어주는 방법으로 동작을 하고 있는 것이다.
그래서 try, finally를 이용을 해가지고
혹시라도 try안에서 exception이 일어날 때
finally로 받아 줘야 된다고 했었다.
그러면 여기 _lockTaken이라는 녀석 (24번쨰 줄에서)
성공했는지 아닌지의 여부를 받아 올 수 있는 것이니까
이렇게 실제로 _lockTaken이 성공을 했다면
이렇게 _lock.Exit를 호출을 해주면될 것이다.
그런데 spinLock이 1. 근성이니까
말그대로 Spin을 하면서 계속 두드리는 방식이기는 한데
근데 몇번 계속 시도를 하다가 진짜 답이 없다고 생각이 들면은
중간에
자기 소유권을 포기하고 조금 쉬다가 온다는 방법
static SpinLock _lock2 = new SpinLock();
이것을 그래서
1번 근성, 2번 양보를 혼합한 방법이라고 보면 된다.
그리고 실제로도 중간중간에 쉬다가 오는 것이 좋다.
그리고
그리고 Mutex가 있는데
이 세가지 중 하나를 골라서 사용을 해야 될텐데
Mutex의 경우 위의 두가지보다 훨씬 무겁다고 했었었다.
굉장히 느리지만 그렇다고 무조건 "단점"만 있는 것은 아니라고 했었다.
Mutex의 경우 굳이 장점을 찾아보자면은
"같은" 프로그램이 아니라고 하더라도
별도의 프로그램끼리도 뭔가 순서를 맞출 수 있는 동기화를 할 수잇는 묘한 "장점"이 있기는 한데,
우리같은 경우 MMORPG 서버를 만든다고 하면은
프로그램 안에서 "멀티쓰레드 환경"으로 돌아가는 것이기 떄문에,
Mutex를 사용해서 프로그램 사이사이에 동기화를 해주는 것이
그렇게 큰 "장점"은 아니기에
Mutex는 그냥 잊어 버러도 된다! ㅋㅋ!
그래서 결국에는
이 첫번째 버젼을 사용을 하거나
아니면
이런 버젼을 사용을 하거나
아니면
직접 만드는 것도 나쁘지 않은 선택이 될 것이다.
( 참고로 사람마다의 스타일이 달라서
기존에 있던 락을 재조립해서 만들어서 사용하는 사람이 있는가 하면
직접 만드는 것을 좋아 하는 사람도 더러 있다. )
그래서 어쨋든 뭐가 더 좋은게 없어서
상황에 맞게 뭐가 좋은지 사용를 해보고
테스트를 해보는게 제일 좋다.
그리고 또 참고로
나중에 서버를 쌓아 올리게 되면은
당연하게도 "멀티 쓰레드 프로그래밍"을 많이 사용을 하기는 할텐데
그런데 거기서 또 갈리는게
핵심적인 Core부분만 멀티쓰레드로 만들 것이냐?
아니면
애당초 게임과 관련된 contents부분도 멀티쓰레드로 만들 것이냐? 는
진짜 별개의 문제이다.
이것은 ㄹㅇ 나중에 선택의 시간이 오게된다.
그래서
만약
일반적으로 컨텐츠를 "멀티 쓰레드"로 만든다. 하면
완전히 모든 코드에서 멀티쓰레드로 돌아 갈 수 있다고 가정을 하면은
"난이도"가 확연하게 어마어마하게 올라간다.
대신 "장점"은
심리스(경계가 없는) MMORPG를 만들 때 조금 이점이 있다.
이런게 아니라
일반적인, 예를 들어 바람의 나라 라던가 뮤같은 게임을 보면 좀
좀? 단위로 나뉘어져있다.
갈 수 있는 지역이 막 구분이 되어있고
그공간안에서 있는 모든 컨텐츠들은 "싱글 쓰레드"로 실행을 시키면
훨씬더 생각하기도 쉽고 버그확률도 줄일 수 있기 때문에
그럴때는 굳이 멀티쓰레드로 갈 필요 없이
전체적인 Core만 멀티쓰레드로 돌리고
핵심적인 코드는 컨텐츠 코드는 싱글 쓰레드로 가는것도 무쁘지 않다.
자 어쩃든 이렇게 락을 사용을 해보았는데
이 두가지 방법 두가지는 내부적인 구현이나 실행속도 차이는 있을 수 는 있지만
기본적인 원리는 굉장히 비슷하다.
한번에 한놈만 들여보내겠다가 같은 원리이다.
그래서 이녀석들의 "철학"은
"상호 배제" 가 되겠다.
즉, 무조건 서로 배제한다.
무조건 나만 들어갈 것이다가
기본적인 아이디어라는 것이다.
그런데 경우에 따라서는 조금 다른 유형의 락이 더 유용할 때가 있다.
예를 들어 보자면은
온라인 게임에서
어떤 1일퀘스트 같은것을 완료를 했을 때,
보상을 받는다고 가정을 해보자.
이런식으로 아이템 세개를 퀘스트 보상으로 받는다고 가정을 했을때
그런데 경우에 따라서 만약 운영하는 팀에서
추가 보상으로 아이템을 이렇게 몇개 더 달 수 있다고 가정을 해보도록 하자.
이런경우에는 운영툴을(만든것을)사용을 해서 보상을 할 수 있는 기능을 넣을 텐데
그렇다는 것은 거꾸로 우리가 코드로 보상을 줘야 한다는 로직이 있다고 가정을 해보도록 하자.
이렇게 보상 클래스가 있으면
안에 보상과 관련된 코드가 있을 것이고
그리고 Reward를 찾는 함수가
이렇게 있다고 가정을 하면은
사실은
이녀석들이 고정은 아니다.
왜냐하면 가끔가다가
이런식으로 운영툴로 이렇게 보상을 추가를 할 수도 있으니까
결국에는
이곳에서 일종의 락이 들어가기는 해야 할것이다
( 왜??? 들어가 야함???)
그래서 사실은 굉장히 아쉬운 부분이
이렇게
운영툴로 보상을 추가를 하는것이
일주일에 한번씩 바뀔 것이다.
그러니까 진짜진짜 가끔만 바뀌고
대부분의 경우에는 정말 변함이 거의 없을 텐데
그럼에도 불구하고 안바뀌 확률이 99.999% 이고
0.001% 확률로 바뀌게 될텐데
이 0.001%확률 때문에
lock을
이렇게 잡는 경우가 굉장히 아쉬운 경우가 있다.
그래가지고 결국
"읽을 떄는"
이녀석을 Get할 때는
동시다발 적으로 접근을 할 수 있다가
이녀석들을 쓸 때(write) 즉, overWrite를 할 때만
이런식으로 lock키워드를 사용해서 상호 배타적으로 막을 수 있으면
굉장히 효율적일거같다라는 생각이 든다.
그래서
99.999% 예의 경우 == 일반적인 경우에는 쿨하게 왔다갔다 하다가
정말 특수한 경우 == 0.001% 확률일 경우에만
서로 막아버리면 된다는 얘기가 될텐데
이렇게 특수한 경우에만 작동하는 락을
별도의 이름으로 부르는데 이것이
"ReaderWriterLock" 이라고 한다.
"RWLock"이라고도 하기도 한다.
C#에도 당연히 구현이 되어있는데
이렇게 두가지 버젼이 있는데
Slim이 붙어 있는것이 조금더 최신 버젼이다.
그래서 이렇게 Slim버젼을 사용을 하면되는데
자 그러면 이제는 만약에
함수가 두가지가 있다고 가정을 하자
이런식으로 id를 받아서 Reward를 찾는 함수와
reward를 추가를 할 수 있는 함수가 있다고 해보자.
static Reward GetRewardById(int id)
이녀석의 대부분의 경우에 사용을 하는것이고
static void AddReward(Reward reward)
얘는 진짜 가끔 사용하는 애라고 가정을 해보자 0.001%확률로
그런데
기존에는 다른 방법이 없었으니까
이런식으로 "상호배타적"으로 락을 잡았을 텐데
이제는
이 아이를 사용을 하면은
GetRewardById 함수는
Enter의 버젼이 두가지가 있는것을 볼 수 있는데
읽을떄는
그냥 이버젼을 사용을 하면 된다는 말이고
이렇게 하면되고
그렇다면
여러개의 쓰레드가
GetRewardById안에 들어왔다고 가정을 하면은
그리고 아무도 이 WriteLock을 잡고 있지 않는다고 하면은
(이게 지금 WriteLock을 의미하는거 같다)
얘내들은 지금 락이 없는것처럼 동시 다발적으로 막 들어 올 수 있다는 것이다.
그러니까 예전의 화장실 예제로 비유를 하자면
이제는 1인 화장실이 아니라
좀 넓은 화장실 이여가지고
일반인들이 동시다발적으로 들어왔다 나갔다 할 수 있는데
만약 어마어마하게 중요한 VIP가 온다고 하면은
나머지 사람들은 다 쫒아내고 그사람 혼자만 화장실을 사용할 수 있게끔
그런 굉장히 자본주의 적인 화장실이라고 생각하면된다.
그래서 이렇게 ReadLock이 있다면 대칭되는 녀석도 있을텐데
만약 누군가가
EnterWriteLock을 물고 있었다 라고하면은
얘내들은 얼씬도 못하는
그런 상황이 될 것이다.
그래서 이렇게 후보가 하나 더 늘었는데
우리가 사용할 수 있는것은
기본적인
lock키워드를 사용하는것,
아니면 SpinLock
아니면
ReaderWriter가 동시에 필요할 경우
이녀석도 있을 것이고
이제는 남은 일은 이중에서 하나를 골라서 사용을 하면된다는 말이다.
그래서
일단 컨텐츠가 많지 않다고 가정을하면은
이런식으로 lock키워드를 사용을 하는것이 우아한 방법이 될 수 있다.
그래서 이까지 하고
다음수업은
ReaderWriterLock의 구현 연습과
InterLock 계열 락을 구현을 하는 연습을 하는 시간을 갖도록 하겠다.
직접 구현을 해본것도 많지도 않고 굉장히 중요하기 때문에!!
-끝-