MMOServer

Read-Write Lock(C++)

이야기prog 2025. 3. 19. 00:21
#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