UnityServer - ReadWriteLock

k_hyun·2022년 10월 27일
0

Unity_Server

목록 보기
11/32

ReadWriteLock

Read 작업은 값을 변경하지 않으니, 하나의 크리티컬 섹션에 여러개의 스레드가 진입해도 일관성 관련 문제가 발생하지 않는다.

Write 작업은 값을 변경 할 수도 있으니, 하나의 크리티컬 섹션에 여러개의 스레드가 진입 할 경우 일관성이 깨질 수 있다.

Read 작업은 여러개의 스레드가 하나의 크리티컬 섹션에 접근 가능하지만, Read하는 도중 값이 변경 되면 안된다.

출처: https://kukuta.tistory.com/35 [HardCore in Programming:티스토리]

하나의 스레드에서 다음과 같이 락을 얻는 상황이 가능하다.

Write -> Write (O),

Write -> Read (O),

Read -> Write (X)

namespace SeverCore
{
    // 재귀적 락 Write->Write (O), Write->Read (O), Read->Write (X)
    // 스핀락 (5000번 -> Yield)
    internal class Lock
    {
        const int EMPTY_FLAG = 0x00000000;
        const int WRITE_MASK = 0x7FFF0000;
        const int READ_MASK = 0x0000FFFF;
        const int MAX_SPIN_COUNT = 5000;

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

WriteLock을 얻는 함수

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

            // 아무도 WriteLock 이나 ReadLock을 획득하고 있지 않은 경우 소유권을 얻는다.
            int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
            while (true)
            {
                for (int i=0; i<MAX_SPIN_COUNT; i++)
                {
                    // CompareExchange를 통해 현재 _flag가 EMPTY면 락을 획득하는 것으로 볼 수 있다.
                    if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                    {
                        _writeCount = 1;
                        return;
                    }
                }
                // 5천번 돌고 안되면 양도.
                Thread.Yield();
            }
        }

WriteLock을 Release하는 함수

        public void WriteUnlock()
        {
            // 락을 풀면 EMPTY_FLAG로 밀어버린다.
            int lockCount = --_writeCount;
            if (lockCount == 0)
                Interlocked.Exchange(ref _flag, EMPTY_FLAG);
        }

ReadLock을 얻는 함수

        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++)
                {
                    int expected = (_flag & READ_MASK);
                    if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
                        return;
                }
                Thread.Yield();
            }
        }

ReadLock을 Release하는 함수

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

0개의 댓글