#pragma once
#include "Types.h"
/*-------------------------
RW SpinLock
-------------------------*/
/*-------------------------------------
[WWWWWWWW][WWWWWWWW][RRRRRRRR][RRRRRRRR]
R -> W (x)
W -> R (o)
---------------------------------------*/
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;
};
class ReadLockGuard {
public:
ReadLockGuard(Lock& lock) :_lock(lock) { _lock.ReadLock(); }
~ReadLockGuard() { _lock.ReadUnLock(); }
private:
Lock& _lock;
};
class WriteLockGuard {
public:
WriteLockGuard(Lock& lock) :_lock(lock) { _lock.WriteLock(); }
~WriteLockGuard() { _lock.WriteUnLock(); }
private:
Lock& _lock;
};
#include "pch.h"
#include "Lock.h"
void Lock::WriteLock()
{
//동일한 쓰레드가 소유하고 있다면 무조건 성공.
const uint32 localThreadId = (_lockFlag & WRITE_THREAD_MASK) >> 16;
if (localThreadId == LThreadId) {
_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 expectedFlag = EMPTY_FLAG;
if (_lockFlag.compare_exchange_strong(OUT expectedFlag, desired)) {
_writeCount++;
return;
}
}
if (::GetTickCount64() - beginTick > ACQUIRE_TIMEOUT_TICK) {
CRASH("LOCK_TIME_OUT");
}
this_thread::yield();
}
}
void Lock::WriteUnLock()
{
if ((_lockFlag & READ_COUNT_MASK) != 0)
CRASH("INVALID_UNLOCK_ORDER");
_writeCount--;
if (_writeCount == 0)
_lockFlag.store(EMPTY_FLAG);
}
void Lock::ReadLock()
{
//동일한 쓰레드가 소유하고 있다면 무조건 성공.
const uint32 localThreadId = (_lockFlag & WRITE_THREAD_MASK) >> 16;
if (localThreadId == LThreadId) {
_lockFlag.fetch_add(1);
return;
}
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;
}
this_thread::yield();
if (::GetTickCount64() - beginTick > ACQUIRE_TIMEOUT_TICK)
CRASH("LOCK_TIME_OUT");
}
}
void Lock::ReadUnLock()
{
if ((_lockFlag.fetch_sub(1) & READ_COUNT_MASK) == 0)
CRASH("MULTIPLE_UNLOCK");
}
mutex의 상호배제를 적절히 조절하면서 사용하기 위하여 read-write lock을 사용한다.
예를 들어서 공유 자원을 모든 thread가 단순히 read할 뿐이라면 상호배제를 할 필요가 없지만, write를 해야한다면 상호배제를 해야한다.
'MMOServer' 카테고리의 다른 글
RefCount(C++) (0) | 2025.03.21 |
---|---|
DeadLock 탐지(C++) (0) | 2025.03.20 |
멀티스레드의 메모리 이슈 (0) | 2025.03.13 |
Future(C++) (0) | 2025.03.13 |
Event, Condition_Variable(C++) (0) | 2025.03.13 |