VisualStudio/C#서버

[C#서버][개념] 임계영역 특수락 - ReaderWriterLockSlim 특수상황 Lock 처리 방법

usingsystem 2022. 10. 26. 17:06
728x90

ReaderWriterLockSlim

이 클래스는 읽기 작업과 쓰기 작업 간의 동시성을 최적화하기 위해 설계되었습니다.

ReaderWriterLockSlim은 일반적으로 읽기 작업이 많이 발생하고 쓰기 작업이 적은 상황에서 사용됩니다. 이 클래스를 사용하면 여러 스레드가 동시에 읽기 작업을 수행할 수 있지만, 쓰기 작업은 배타적으로 수행됩니다. 다시 말해, 한 스레드가 쓰기 작업을 수행하는 동안에는 다른 스레드는 읽기나 쓰기 작업을 할 수 없습니다.

 

평상시에는 Read락을 사용하다 특수한경우 어떤 쓰레드가 WriteLock을 사용하면 ReadLock을 사용하는 다른 모든 쓰레드는 자원을 사용하지 못하게 된다.

   class Reward
        {
        }
static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

        static Reward GetRewardByID(int id)//일반적인 상황
        {
            _lock.EnterReadLock();


            _lock.ExitReadLock();

            return null;
        }

        static void AddReward(Reward reward)//특수목적 상황
        {
            _lock.EnterWriteLock();


            _lock.ExitWriteLock();
        }

        static void Main(string[] arg)
        {
        }

 


ReaderWriterLockSlim 직접 만들어 보기

 /// <summary>
    /// 재귀적 락을 허용할지 (yes)  writeLock -> WriteLock Ok, WriteLock -> ReadLock Ok, ReadLock -> WriteLock No
    /// 스핀락 검색 (5000번 -> Yield
    /// </summary>
    internal class Lock
    {
        const int EMPTY_FLAG = 0x00000000;
        const int WRITE_MASK = 0x7FFF0000;
        const int READ_MASK = 0x0000FFFF;
        const int MAX_SPIN_COUNT = 5000;
        //[Unsed(1)] [WriteThreadId(15비트)] [ReadCount(16비트)]
        int _flag;
        int _writeCount = 0;

        public void WriteLock()
        {
            //동일 쓰레드가 WriteLock을 이미 획득하고 있는지 확인 재귀적호출
            int lockThreadId = (_flag & WRITE_MASK) >> 16;
            if(Thread.CurrentThread.ManagedThreadId == lockThreadId)
            {
                _writeCount++;
                return;
            }

            //아무도 WriteLock or ReadLock을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다.
            int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                    {
                        _writeCount = 1; 
                        return;
                    }
                    ////위에소스와 동일하지만 이렇게 사용하면 어셈블리에서 3번 작업을 하기 때문에 안됨.
                    //if(_flag == EMPTY_FLAG)//시도를 해서 성공하면 Return
                    //    _flag = desired;//현재 쓰레드id입력
                }
                Thread.Yield();
            }
        }
        public void WriteUnLock()
        {
            int lockCount = --_writeCount;
            if (lockCount == 0)
                Interlocked.Exchange(ref _flag, EMPTY_FLAG);
        }
        public void ReadLock()
        {
            //동일 쓰레드가 WriteLock을 이미 획득하고 있는지 확인 재귀적호출
            int lockThreadId = (_flag & WRITE_MASK) >> 16;
            if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
            {
                Interlocked.Increment(ref _flag);
                return;
            }
            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    int expected = (_flag & READ_MASK);
                    if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
                        return;

                    //if ((_flag & WRITE_MASK) == 0)
                    //{
                    //    _flag = _flag+1;
                    //    return;
                    //}
                }
                Thread.Yield();
            }
        }
        public void ReadUnLock()
        {
            Interlocked.Decrement(ref _flag);
        }
    }
728x90