(C# : Server) ReaderWriterLock 구현

Posted by : at

Category : Charp-Server


ReaderWriterLock을 직접구현해보자

// Lock.cs

// 여기서는 재귀적 락은 허용하지 않음
// 스핀락의 경우 5000번 이후 Yield
class Lock
{
    const int EMPTY_FLAG = 0x00000000;
    const int WRITE_FLAG = 0x7FFF0000;
    const int READ_FLAG = 0x0000FFFF;
    const int MAX_SPIN_COUNT = 5000;

    // [Unused(1비트)] [WriteThreadId(15비트)] [ReadCount(16비트)]
    int _flag = EMPTY_FLAG;

    public void WriteLock()
    {
        int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_FLAG;
        while(true)
        {
            for(int i = 0; i < MAX_SPIN_COUNT; i++)
            {
                /*
                if(_flag == EMPTY_FLAG)
                    _flag = desired;
                */
                if( Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                    return;
            }

            Thread.Yield();
        }
    }

    public void WriteUnlock()
    {
        Interlocked.Exchange(ref _flag, EMPTY_FLAG);
    }

    public void ReadLock()
    {
        while(true)
        {
            for(int i = 0; i < MAX_SPIN_COUNT; i++)
            {
                /*
                if((_flag & WRITE_MASK) == 0)
                {
                    _flag = _flag + 1;
                    return;
                }
                */
                int expected = (_flag & READ_MASK);
                if(Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
                    return;
            }

            Thread.Yield();
        }
    }

    public void ReadUnlock()
    {
        Interlocked.Decrement(ref _flag);
    }
}

재귀적 락을 허용하는 버전을 구현해보자

// Lock.cs

// 단, WriteLock에서 WriteLock, ReadLock을 재귀적으로 잡는 것은 지원
// ReadLock에서 WriteLock을 재귀적으로 잡는 것은 지원안함

// 스핀락의 경우 5000번 이후 Yield
class Lock
{
    const int EMPTY_FLAG = 0x00000000;
    const int WRITE_FLAG = 0x7FFF0000;
    const int READ_FLAG = 0x0000FFFF;
    const int MAX_SPIN_COUNT = 5000;

    // [Unused(1비트)] [WriteThreadId(15비트)] [ReadCount(16비트)]
    int _flag = EMPTY_FLAG;
    int _writeCount = 0;

    public void WriteLock()
    {
        // 동일 쓰레드가 WriteLock을 획득하는지 확인
        int lockThreadId = (_flag & WRITE_MASK) >> 16;
        if(Thread.CurrendThread.ManagedThreadId == lockThreadId)
        {
            _writeCount++;
            return;
        }

        int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_FLAG;
        while(true)
        {
            for(int i = 0; i < MAX_SPIN_COUNT; i++)
            {
                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()
    {
        int lockThreadId = (_flag & WRITE_MASK) >> 16;
        if(Thread.CurrendThread.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;
            }

            Thread.Yield();
        }
    }

    public void ReadUnlock()
    {
        Interlocked.Decrement(ref _flag);
    }
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links