sleepyotter
Lock 구현 본문
코드는 강좌를 구매해주시길 바란다. 구현 방법만 기록.
Lock은 3가지로 나눌 수 있다.
1. SpinLock
2. RandomLock?(Context Switching)
3. AutoResetEvent
SpinLock
잠금이 풀릴 때까지 계속 루프를 돌며 대기한다. 이를 구현하기 위해 bool _locked를 두고, _locked가 true이면 while로 기다리다가 while을 빠져나오면 _locked를 다시 true로 바꾸는 것을 생각해 볼 수 있는데, 이상적으로 작동하지 않는다.
왜냐하면 while에서 _locked를 체크하고, _locked를 바꾸는 부분까지 Atomic하게 이루어져야 한다. 그렇지 않으면 내가 true로 바꾸려는 순간 다른애도 "어? lock 열렸네?" 하고 들어와서 나랑 동시에 true로 바꿔버릴 수 있기 때문이다.
이런 경우 2개의 스레드가 동시에 Lock을 획득해버린 상황이다.
이를 해결하기 위해, Interlocked.Exchange를 사용해볼 수 있다. 혹은 CompareExchange도 가능하다.
이를 CAS, Compare-And-Swap 이라 한다.
Context Switching
누군가가 락을 이미 소유하고 있다면 잠시 대기했다가 다시 확인해보는 방식
대기하는 방식에는 3가지 방식이 있다.
Sleep(1): 무조건 휴식. 1ms 정도 쉬겠다는 건데, 정확히 1ms는 아니고 운영체제가 알아서 최대한 비슷하게 해준다.
Sleep(0): 조건부 양보. 자신보다 우선순위가 높은 스레드가 cpu time을 원하고 있다면 cpu 를 양보하고, 다시 나에게 cpu time이 돌아오면 락을 확인해본다. 다만 자신의 우선순위가 높다면 내 아레 스레드들의 기아 상태를 유발할 가능성이 있다.
Yield(): 무조건 양보. 지금 cpu time을 원하는 애가 있다면 cpu 가져다 쓰세요. 실행 가능한 스레드가 없다면 남은 cpu time을 모두 소진한다.
cpu가 어떤 스레드의 cpu타임이 소진됐을때, 다른 스레드를 실행시키기 전에 현재 스레드의 정보를 PCB에 저장하고 대기 상태로 전환시키고 준비 상태인 다른 스레드로 이동하는데, 이 과정을 context swtiching 이라 한다.
프로세스 상태 전이도에서
실행상태->대기상태 이동은 I/O 요청 발생 시 이동한다.
실행상태->준비상태 이동은 인터럽트 발생 시 이동한다.
대기상태->준비상태 이동은 I/O요청이 완료됐을 시 이동한다.
CPU가 스레드를 준비상태에서 실행상태로 이동시키기 위해서는 PCB에 있는 정보를 모두 뽑아와 RAM에서 레지스터로 복원시켜야 한다.
AutoResetEvent
톨게이트를 생각하면 된다. 차가 한 대 지나가면 차단바가 내려오고의 반복이다.
C#에서 기본 제공하는 AutoResetEvent를 활용한다.
AutoResetEvent _available = new AutoResetEvent(true); 와 같이. true면 통과 가능상태, false면 불가능.
다른 락들과 마찬가지로 연산 전에 획득하고 연산 후에 풀어준다. 다만 _available.WaitOne() 하여 커널이 나에게 통과 가능하다고 알려주면 통과하고 락이 잠기는 방식이기 때문에, SpinLock에 비해 느리다. -> 커널을 다녀오는 것이 꽤 무거운 연산이기 때문.
비슷한 사용법을 가진 녀석을 Mutext가 있는데, 커널까지 가는 커널 동기화 객체이다. 어지간해서는 AutuResetEvent까지만 쓰고 Mutext는 지양하도록 하자. 더 느리다.
Mutex는 다른 프로그램들 사이에서도 커널을 통해 동기화를 할 수 있다는 장점이 있으나, 게임이나 MMORPG서버의 경우에는 하나의 프로세스 위에서 멀티 스레드로 동작하기 때문에 무의미한 장점이다.
'프로그래밍 > C# 게임서버' 카테고리의 다른 글
Thread Local Storage (0) | 2021.09.07 |
---|---|
ReaderWriterLock (0) | 2021.09.07 |
DeadLock (0) | 2021.09.07 |
Lock 기초 (0) | 2021.09.07 |
Interlocked (0) | 2021.09.07 |