🔐 1. Reader-Writer Lock이란?

  • Read는 다수 동시 허용 (Shared Lock)
  • Write는 오직 하나만 허용 (Exclusive Lock)
  • 단, Read → Write 전환은 허용 X
  • 동일한 쓰레드가 Write를 가진 상태에서 Read는 허용 O
    → 즉, Write가 Read를 포함할 수 있도록 재귀적 Lock 허용

🧠 2. 핵심 아이디어: 32bit 비트 마스크 설계

[WWWWWWWW][WWWWWWWW][RRRRRRRR][RRRRRRRR]
 W: WriteThreadId (16비트)
 R: ReadCount      (16비트)
  • 상위 16비트: WriteLock을 가진 Thread의 ID 저장
  • 하위 16비트: 현재 ReadLock을 잡은 쓰레드의 수

🔧 3. Lock 클래스 정의

class Lock
{
    enum : uint32
    {
        ACQUIRE_TIMEOUT_TICK = 10000,
        MAX_SPIN_COUNT = 5000,
        WRITE_THREAD_MASK = 0xFFFF'0000,
        READ_COUNT_MASK = 0x0000'FFFF,
        EMPTY_FLAG = 0x0000'0000
    };

public:
    void WriteLock();
    void WriteUnlock();
    void ReadLock();
    void ReadUnlock();

private:
    Atomic<uint32> _lockFlag = EMPTY_FLAG;
    uint16 _writeCount = 0; // 재귀 횟수
};
  • _lockFlag: Read/Write 상태를 동시에 표현
  • _writeCount: 동일 Thread가 WriteLock 여러 번 호출하는 경우를 위한 카운트

🔄 4. WriteLock() 함수 분석

void Lock::WriteLock()
{
    const uint32 lockThreadId = (_lockFlag.load() & WRITE_THREAD_MASK) >> 16;
    if (LThreadId == lockThreadId)
    {
        _writeCount++;
        return;
    }

    const int64 beginTick = ::GetTickCount64();
    const uint32 desired = (LThreadId << 16) & WRITE_THREAD_MASK;

    while (true)
    {
        for (uint32 spinCount = 0; spinCount < MAX_SPIN_COUNT; ++spinCount)
        {
            uint32 expected = EMPTY_FLAG;
            if (_lockFlag.compare_exchange_strong(expected, desired))
            {
                _writeCount++;
                return;
            }
        }

        if (::GetTickCount64() - beginTick >= ACQUIRE_TIMEOUT_TICK)
            CRASH("LOCK_TIMEOUT");

        this_thread::yield(); // 양보
    }
}

✅ 특징 요약:

  • 같은 쓰레드가 WriteLock 재호출: WriteCount++
  • 아무도 Lock을 소유하지 않으면: CAS로 상위 16bit에 ID 삽입

🔓 5. WriteUnlock() 함수 분석

void Lock::WriteUnlock()
{
    if ((_lockFlag.load() & READ_COUNT_MASK) != 0)
        CRASH("INVALID_UNLOCK_ORDER");

    const int32 lockCount = --_writeCount;
    if (lockCount == 0)
        _lockFlag.store(EMPTY_FLAG);
}

✅ 주의: ReadLock이 남아 있으면 WriteUnlock 불가능!


📖 6. ReadLock() 함수 분석

void Lock::ReadLock()
{
    const uint32 lockThreadId = (_lockFlag.load() & WRITE_THREAD_MASK) >> 16;
    if (LThreadId == lockThreadId)
    {
        _lockFlag.fetch_add(1); // 같은 쓰레드가 WriteLock 중이라면 ReadLock OK
        return;
    }

    const int64 beginTick = ::GetTickCount64();
    while (true)
    {
        for (uint32 spin = 0; spin < MAX_SPIN_COUNT; ++spin)
        {
            uint32 expected = _lockFlag.load() & READ_COUNT_MASK;
            if (_lockFlag.compare_exchange_strong(expected, expected + 1))
                return;
        }

        if (::GetTickCount64() - beginTick >= ACQUIRE_TIMEOUT_TICK)
            CRASH("LOCK_TIMEOUT");

        this_thread::yield();
    }
}

✅ 특징 요약:

  • WriteLock이 없을 때만 ReadLock 허용
  • ReadCount 증가만으로 Lock을 표현

🚪 7. ReadUnlock() 함수

void Lock::ReadUnlock()
{
    if ((_lockFlag.fetch_sub(1) & READ_COUNT_MASK) == 0)
        CRASH("MULTIPLE_UNLOCK");
}
  • 감소 전 값이 0이면, 중복 Unlock 에러 발생

📦 8. RAII를 위한 LockGuard 클래스

class ReadLockGuard
{
public:
    ReadLockGuard(Lock& lock, const char* name) : _lock(lock), _name(name) { _lock.ReadLock(name); }
    ~ReadLockGuard() { _lock.ReadUnlock(_name); }

private:
    Lock& _lock;
    const char* _name;
};

class WriteLockGuard
{
public:
    WriteLockGuard(Lock& lock, const char* name) : _lock(lock), _name(name) { _lock.WriteLock(name); }
    ~WriteLockGuard() { _lock.WriteUnlock(_name); }

private:
    Lock& _lock;
    const char* _name;
};

🛠 9. 매크로 정의로 간결한 사용

#define USE_LOCK                Lock _locks[1];
#define READ_LOCK               ReadLockGuard readLockGuard(_locks[0], __FUNCTION__);
#define WRITE_LOCK              WriteLockGuard writeLockGuard(_locks[0], __FUNCTION__);

🧪 10. 실전 테스트 코드

class TestLock
{
    USE_LOCK;

public:
    int32 TestRead()
    {
        READ_LOCK;
        if (_queue.empty()) return -1;
        return _queue.front();
    }

    void TestPush()
    {
        WRITE_LOCK;
        _queue.push(rand() % 100);
    }

    void TestPop()
    {
        WRITE_LOCK;
        if (!_queue.empty()) _queue.pop();
    }

private:
    queue<int32> _queue;
};

TestLock testLock;

🔁 11. 쓰레드 테스트 실행

void ThreadRead()
{
    while (true)
    {
        cout << testLock.TestRead() << endl;
        this_thread::sleep_for(1ms);
    }
}

void ThreadWrite()
{
    while (true)
    {
        testLock.TestPush();
        this_thread::sleep_for(1ms);
        testLock.TestPop();
    }
}

int main()
{
    for (int32 i = 0; i < 2; ++i)
        GThreadManager->Launch(ThreadWrite);

    for (int32 i = 0; i < 5; ++i)
        GThreadManager->Launch(ThreadRead);

    GThreadManager->Join();
}

profile
李家네_공부방

0개의 댓글