[WWWWWWWW][WWWWWWWW][RRRRRRRR][RRRRRRRR]
W: WriteThreadId (16비트)
R: ReadCount (16비트)
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 여러 번 호출하는 경우를 위한 카운트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(); // 양보
}
}
✅ 특징 요약:
WriteCount++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 불가능!
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();
}
}
✅ 특징 요약:
void Lock::ReadUnlock()
{
if ((_lockFlag.fetch_sub(1) & READ_COUNT_MASK) == 0)
CRASH("MULTIPLE_UNLOCK");
}
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;
};
#define USE_LOCK Lock _locks[1];
#define READ_LOCK ReadLockGuard readLockGuard(_locks[0], __FUNCTION__);
#define WRITE_LOCK WriteLockGuard writeLockGuard(_locks[0], __FUNCTION__);
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;
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();
}