Coding Memo

Lock 구현 (Read-Write Lock) 본문

Game Server (C++)

Lock 구현 (Read-Write Lock)

minttea25 2022. 9. 26. 16:32

본 포스팅은 인프런에 등록되어 있는 Rockiss 님의 강의를 보고 간단하게 정리한 글입니다.


C++ 에서도 C11 부터 자체적으로 std에 lock을 지원해주고 있다.

 

기본적인 lock 기능만 사용할 경우 불편한 점이 몇가지 있다.

1. 표준 mutex로는 재귀적인 lock을 잡을 수 없다. (recursive_mutex가 있긴하다)

2. 메모리를 동시에 쓰거나 읽을 때는 반드시 원자적으로 접근해야하지만, 읽기만 할때는 반드시 원자적으로 접근 할 필요가 없다. (-> 많은 쓰레드들이 동시에 어떤 값을 읽기만 하는 것은 lock이 필요하지 않을 수 있다.)

 

따라서 위와 같은 불편한 점과 편의를 위해 직접 구현하기도 한다.


구현 핵심

 

lock을 가지고 있는 쓰레드들을 구분하기 위해 32bit 사용

구현

상위 16bit는 Write를 위한 공간으로,

하위 16bit는 Read를 위한 공간으로 사용

 

Write 시, lock을 얻은 쓰레드의 id를 mask로 필터링 하여 상위 16bit에 저장


#pragma once

#include <atomic>
#include <Windows.h>
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

class Lock
{
	enum : unsigned int
	{
		ACQUIRE_TIMEOUT_TICK = 10000,
		MAX_SPIN_COUNT = 5000,
		WRITE_THREAD_MASK = 0xFFFF0000,
		READ_COUNT_MASK = 0x0000FFFF,
		EMPTY_FLAG = 0x00000000,
	};
public:
	void WriteLock();
	void WriteUnlock();
	void ReadLock();
	void ReadUnlock();
private:
	atomic<unsigned int> _lockFlag = EMPTY_FLAG;
	unsigned short _writeCount = 0;
};
#include "Lock.h"

thread_local unsigned int LThreadId = 0;

void Lock::WriteLock()
{
	// 이미 소유
	const unsigned int lockThreadId = (_lockFlag.load() & WRITE_THREAD_MASK) >> 16;
	if (LThreadId == lockThreadId)
	{
		_writeCount++;
		return;
	}

	const long long beginTick = ::GetTickCount64();

	// 아무도 소유하고 있지 않을 때, 경합해서 소유권 획득
	const long long desired = ((LThreadId << 16) & WRITE_THREAD_MASK);

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

		if (::GetTickCount64() - beginTick >= ACQUIRE_TIMEOUT_TICK)
		{
			cout << "Time OUT!" << endl;
			exit(-1);
		}

		this_thread::yield();
	}
}

void Lock::WriteUnlock()
{
	// readlock을 다 풀 기 전에는 writeunlock 불가
	if ((_lockFlag.load() & READ_COUNT_MASK) != 0)
	{
		cout << "Invalid_Unlock_Order" << endl;
		exit(-1);
	}

	const unsigned int lockCount = --_writeCount;
	if (lockCount == 0)
	{
		_lockFlag.store(EMPTY_FLAG);
	}
}

void Lock::ReadLock()
{
	// 이미 소유
	const unsigned int lockThreadId = (_lockFlag.load() & READ_COUNT_MASK) >> 16;
	if (LThreadId == lockThreadId)
	{
		_lockFlag.fetch_add(1);
		return;
	}

	const long long beginTick = ::GetTickCount64();

	while (true)
	{
		unsigned int expected = (_lockFlag.load() & READ_COUNT_MASK);
		if (_lockFlag.compare_exchange_strong(expected, expected + 1))
		{
			_lockFlag.fetch_add(1);
			return;
		}

		if (::GetTickCount64() - beginTick >= ACQUIRE_TIMEOUT_TICK)
		{
			cout << "Time OUT!" << endl;
			exit(-1);
		}
		this_thread::yield();
	}
}

void Lock::ReadUnlock()
{
	if ((_lockFlag.fetch_sub(1) & READ_COUNT_MASK) == 0)
	{
		cout << "Multiple Unlock" << endl;
		exit(-1);
	}
}

 

'Game Server (C++)' 카테고리의 다른 글

Memory Pool  (0) 2022.10.09
메모리 할당 - STL Allocator  (0) 2022.10.09
메모리 할당 - Stomp Allocator  (0) 2022.10.06
메모리 할당 - Allocator  (0) 2022.10.06
Reference Counting  (0) 2022.09.30