Reader-Writer Lock의 구현하여 사용하는 이유
경우에 따라서 Lock을 걸기엔 아까운 경우가 발생하기도 한다.
예를 들어서 데이터의 변경이 0.0001퍼센트로 일어난다고 할 때, 이 경우를 위해서 Read하는 경우를 한명씩 들여보내는 것은 매우 비효율적이다. 이를 해결하기 위해서 표준의 Lock을 사용하는 것이 아닌 직접 만든 Lock을 사용하게된다.
/*--------------------
R/W SpinkLock
--------------------*/
/*--------------------
[WWWWWWWW][WWWWWWWW][RRRRRRRR][RRRRRRR]
W : WrieteFlag (Exclusive Lock Owner ThreadId)
R : ReadFlag ( Shared Lock Count)
--------------------*/
//동일 쓰레드에서
// W -> W (O)
// W -> R (O)
// R -> W (X)
class Lock
{
enum : uint32
{
ACQUIRE_TIMEOUT_TICK = 10000,
MAX_SPIN_COUNT = 5000,
WRITE_THREAD_MASK = 0xFFFF'0000,
READ_COUBT_MASK = 0x0000'FFFF,
EMPTY_FLAG = 0x0000'0000
}
public:
void WriteLock();
void WriteUnlock();
void ReadLock();
void ReadUnlock();
private:
Atomic<uint32> _lockFlag;
uint16 _writeCount = 0;
};
/*----------------------
LockGuards
---------------------*/
class ReadLockGuard
{
public:
ReadLockGuard(Lock& lock) : _lock(lock) { _lock.ReadLock(); }
~ReadLockGuard() { _lock.ReadUnlock(); }
private:
Lock& _lock;
};
class WriteLockGuard
{
public:
WirteLockGuard(Lock& lock) : _lock(lock) {_lock.WriteLock();}
~WriteLockGuard() { _lock.WriteUnlock();}
private:
Lock& _lock;
void Lock::WriteLock()
{
// 동일한 쓰레드가 소유하고 있다면 무조건 성공.
const uint32 lockThreadId = (_lockFlag.load() & WRITE_THREAD_MASK) >> 16;
if(LThreadId == lockThreadId)
{
_writeCount++;
return;
}
// 아무도 소유 및 공유하고 있지 않을 때, 경합해서 소유권을 얻는다.
if(_lockFlag == EMPTY_FLAG)
{
const uint32 desired = ((LThreadId << 16) & WRITE_THREAD_MASK);
_lockFlag = desired;
}
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(OUT expected,desired))
{
_writeCount++;
return;
}
}
if(::GetTickCount64() - beginTick >=ACQUIRE_TIMEOUT_TICK)
CRASH("LOCK_TIMEOUT");
this_thread::yield();
}
}
void Lock::WriteUnlock()
{
//ReadLock 다 풀기 전에는 WriteUnlock 불가능.
if(_lockFlag.load() & READ_COUNT_MASK != 0)
CRASH("INVALID_UNLOCK_ORDER");
const int32 lockCount = --_writeCount;
if (lockCount == 0)
_lockFlag.store(EMPTY_FLAG);
}
void Lock::ReadLock()
{
//동일한 쓰레드가 소유하고 있다면 무조건 성공
const uint32 lockThreadId = (_lockFlag.load() & WRITE_THREAD_MASK) >>16;
if(LThreadId == lockThreadId)
{
_lockFlag.fetch_add(1);
return;
}
// 아무도 소유하고 있지 않을 때 경합해서 공유 카운트를 올린다.
const int64 beginTick = ::GetTickCount64();
While (true)
{
for (uint32 spinCount = 0; spinCount < MAX_SPIN_COUNT ; spinCount++)
{
uint32 expected = (_lockFlag.load() & READ_COUNT_MASK);
if(_lockFlag.compare_exchange_strong(OUT 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");
}