멀티 쓰레드
복습! 🤠
락을 구현하는 때에는 3가지 방법이 있다. 1. 근성, 2. 양보, 3. 갑질. ❗
근성은 계속 빙글빙글 lock을 돌며 대기하는 것이고,
양보는 잠시 쉬었다가 다시 와서 획득 시도를 요청하는 것이다.
마지막으로 갑질은 직원을 시켜 알려달라 요구하는 것이다.
일전에 얘기했던 것과 같이 3가지 방법 중 뭐가 가장 좋고, 나쁘다 라고 말할 수 없으며 각각의 장점이 각각의 단점을 보완하는 형태이기 때문에 본인에게 필요한 방법을 사용하기로 하자.
그 동안 사용했던 Lock
Lock 👉 내부적으로 Monotor 클래스를 이용해 편리하게 사용 가능한 락이다. (상호배제)
static object key = new object();
static void Main(string[] args)
{
lock(key)
{
}
}
SpinLock 👉 이전에는 While, InterLocked 등을 이용하여 SpinLock을 구현했으나 C# 에서는 SpinLock 클래스가 사실 존재하니 아래와 같이 사용한다.
static SpinLock spin = new SpinLock();
static bool lockTaken;
static void Main(string[] args)
{
try
{
// lock이 성공할 경우 lockTaken 을 true로 변경
spin.Enter(ref lockTaken);
}
// 정상적으로 Exit이 안될 경우를 위한 예외처리
finally
{
if (lockTaken)
spin.Exit();
}
}
Mutex 👉 느리지만 별도의 프로그램끼리 동기화 작업을 처리할 때 사용하기 적절하다. 하지만 실시간 MMORPG의 경우 적합한 방법이 아니기에 많이 사용되지 않는다.
그 외에도 위의 방법을 적절히 섞어 본인만의 Lock을 만드는 방법이 존재한다. 👉 사실 처음 배울때에는 직접 만드는 것이 배우는 것이 많기에 좋고, 익숙해지면 기존에 만들어 진것이 어떻게 작동하는 지 알기 때문에 기존에 있는 것을 사용하는 걸 추천한다.
게임 서버를 이용해 구현을 하다보면 문제가 생기는데 바로 게임 내 핵심 기능인 '코어'만 멀티 쓰레드로 구현할 것인지, 아니면 '콘텐츠' 또한 멀티 쓰레드로 구현할 것인지 정해야 하는 선택해야 되는 상황이 직면하게 되는 것이다. 콘텐츠도 멀티 쓰레드를 이용해 구현 할 경우 난이도가 엄청나게 올라가지만, 대신 장점으로 경계가 적은 심리스 게임을 구현할 때에는 멀티 쓰레드를 사용하기도 한다. 그러나 바람의 나라, 뮤 같은 게임을 보면 갈 수 있는 지역이 구분되고, 공간 안에서 따로 구동이 가능 할 때에는 싱글 쓰레드를 사용하기도 한다.
ReaderWriterLock
위 3가지의 락과 달리 경우에 따라 다른 형태의 락이 필요할 때가 있다. 예를 들어 온라인 게임에서 퀘스트를 완수 했을 때 보상을 받는다고 생각해보자. 아이템 3개를 고정으로 받는데, 가끔 랜덤한 확률로 경우에 따라 N개의 추가 보상을 받을 수 있다고 가정해보자.
class Program
{
static object _lock = new object();
class Reward
{
}
static Reward GetRewardById(int id)
{
lock (_lock)
{
}
return null;
}
}
위와 같은 환경에서 GetRewardById 메서드를 통해 3개의 리워드를 찾아오는 데에는 문제가 없다. 그러나 랜덤한 확률로 추가 보상을 받을 수 있는 상황에서 문제가 생긴다. 왜냐하면 추가하는 과정(Write)에서 멀티 쓰레드의 문제가 생길 수 있기 때문에 lock을 걸어줘야 하기 때문이다.
그러나 랜덤으로 추가 보상을 받는 상황은 매우 드물기 때문에 궂이 lock을 이용하여 구현해야 할까? 라는 상황이 놓이게 된다. 따라서 이 때 아이템 보상을 주기 위해 정보를 읽는 행위(Read)의 대해서는 lock이 없는 것처럼 자유롭게 사용하고, 보상을 추가할 때(Write)에는 lock을 통해 사용하는 구조로 변경하면 보다 효율적인 코드를 짤 수 있다. 이를 ReaderWriterLock(이하 = RWLock)라고 하며, C#에서도 이미 구현되어 있는 상태이다.
class Program
{
static ReaderWriterLockSlim _lock3 = new ReaderWriterLockSlim();
class Reward
{
}
static Reward GetRewardById(int id)
{
_lock3.EnterReadLock();
_lock3.ExitReadLock();
return null;
}
static void AddReward(Reward reward)
{
_lock3.EnterWriteLock();
_lock3.ExitWriteLock();
}
static void Main(string[] args)
{
}
}
위 소스 코드에서 GetRewardById 메서드에서 사용하는 EnterReadLock(), ExitReadLock() 메서드를 이용해 작업을 처리하면 WriteLock에서 잡아둔 락이 없는 상태이면 lock이 없는 것처럼 자유롭게 사용할 수 있다. 이는 보너스가 없는 보상을 주는 것이라고 생각하면 된다.
그러나 만약 EnterWriteLock()와 ExitWriteLock()가 락을 선점하게 된다면 즉 보너스 보상이 처리 되기 이전까지 보너스가 없는 보상의 처리는 잠시 멈추고, 보너스 보상 처리가 다 진행되기 전까지는 lock을 통과하지 못하는 상황이 되는 것이다.
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 4-2-15. 멀티쓰레드 프로그래밍 : TLS(Thread Local Storage) (1) | 2023.09.18 |
---|---|
Part 4-2-14. 멀티쓰레드 프로그래밍 : ReaderWriterLock 구현 연습 (0) | 2023.09.18 |
Part 4-2-12. 멀티쓰레드 프로그래밍 : 이벤트(AutoResetEvent, ManualResetEvent), 뮤텍스 (0) | 2023.09.16 |
Part 4-2-11. 멀티쓰레드 프로그래밍 : 문맥 교환(Context Switching) (0) | 2023.09.16 |
Part 4-2-10. 멀티쓰레드 프로그래밍 : SpinLock (0) | 2023.09.13 |