공부/인프런 - Rookiss

Part 4-2-13. 멀티쓰레드 프로그래밍 : ReaderWriterLock

셩잇님 2023. 9. 16. 23:33
반응형

 

 

멀티 쓰레드

 

복습! 🤠

 

 락을 구현하는 때에는 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을 통과하지 못하는 상황이 되는 것이다.

 

보상 멈춰!

 

 

 

반응형