302호 컴퓨터실/강의 자료

ReaderWriterLock Sample

삼층거주자 2024. 10. 2. 00:30
728x90
반응형
namespace ServerCore;

// 재귀적 락을 허용 (Yes) WriteLock -> WriteLock OK, WriteLock -> ReadLock OK, ReadLock -> WriteLock No
// 스핀락 정책 (5000번 -> Yield)
public class Lock
{
    private const int EMPTY_FLAG = 0x00000000;
    private const int WRITE_MASK = 0x7FFF0000;
    private const int READ_MASK = 0x0000FFFF;
    private const int MAX_SPIN_COUNT = 5000;
    
    // [Unused(1)] [WriteThreadId(15)] [ReadCount(16)]
    private int _flag;
    private 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++)
            {
                // 시도를 해서 성공하면 return
                if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                {
                    _writeCount = 1;
                    return;
                }
            }
            
            Thread.Yield();
        }
    }

    public void WriteUnlock()
    {
        int lockCount = --_writeCount;
        if (lockCount == 0)
            Interlocked.Exchange(ref _flag, EMPTY_FLAG);
    }

    public void ReadLock()
    {
        // 동일 쓰레드가 ReadLock을 이미 획득하고 있는지 확인
        int lockThreadId = (_flag & WRITE_MASK) >> 16;
        if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
        {
            Interlocked.Increment(ref _flag);
            return;
        }
        
        // 아무도 WriteLock 을 획득하고 있지 않으면 ReadCount를 1 늘린다.
        while (true)
        {
            for (int i = 0; i < MAX_SPIN_COUNT; i++)
            {
                // 만약 WriteThreadId 가 0 이 아닌 경우에는 무조건 fail 임
                // writeLock 을 잡았다 = _flag의 WriteThreadId가 0 이 아님.
                // 밑에서 비교하면 무조건 fail
                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);
    }
}
반응형