KoreanFoodie's Study

[C++ 게임 서버] 1-16. Lock-Based Stack/Queue 본문

Game Dev/Game Server

[C++ 게임 서버] 1-16. Lock-Based Stack/Queue

GoldGiver 2023. 7. 25. 11:29

Rookiss 님의 '[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 를 들으며 배운 내용을 정리하고 있습니다. 관심이 있으신 분은 꼭 한 번 들어보시기를 추천합니다!

[C++ 게임 서버] 1-16. Lock-Based Stack/Queue

핵심 :

1. Lock-Based Queue/Stack 자료구조를 활용하면, 여러 쓰레드가 해당 자료구조를 활용할 때 불필요한 Lock 을 추가로 잡을 필요가 없이, 싱글쓰레드 환경에서처럼 Push/Pop 을 사용할 수 있다.

2. Push/Pop 함수 내부적으로 lock_guard 를 잡아주면 된다.

3. WaitPop 의 경우, Condition Variable 을 이용하여 구현한다.

Queue 나 Stack 같은 자료구조에 여러 쓰레드가 동시에 접근해야 한다고 가정해 보자.

지금까지 배운 것을 응용하면, 각 쓰레드가 특정 자료구조에 접근할 때마다 락을 걸고 풀기를 반복해야 하겠지만... 이는 사실 매우 귀찮을 수 있다.

그냥 싱글 쓰레드 환경에서 Push/Pop 을 간단히 쓰듯 사용하고 싶을 때는 어떻게 해야 할까? 그럴 경우, 자료 구조 자체가 Lock 을 사용하도록 만들면 된다!

즉, Push/Pop 을 할 때 알아서 Lock 을 잘 잡았다가 풀 수 있도록 만들어 주면 되는데... 실제 코드는 다음과 같이 나올 것이다.

 

template<typename T>
class LockStack
{
public:
	LockStack() { }

	LockStack(const LockStack&) = delete;
	LockStack& operator=(const LockStack&) = delete;

	void Push(T value)
	{
		lock_guard<mutex> lock(_mutex);
		_stack.push(std::move(value));
		_condVar.notify_one();
	}

	bool TryPop(T& value)
	{
		lock_guard<mutex> lock(_mutex);
		if (_stack.empty())
			return false;

		// empty -> top -> pop
		value = std::move(_stack.top());
		_stack.pop();
		return true;
	}

	void WaitPop(T& value)
	{
		unique_lock<mutex> lock(_mutex);
		_condVar.wait(lock, [this] { return _stack.empty() == false; });
		value = std::move(_stack.top());
		_stack.pop();
	}

private:
	stack<T> _stack;
	mutex _mutex;
	condition_variable _condVar;
};

특별할 것은 없고, 그냥 예전에 하던 대로 lock_guard 를 잡아주는데, 이제는 함수 내부에서 잡아준다는 차이가 있다.

그리고 WaitPop 의 경우, condition_variable 을 활용하여 TryPop 과는 조금 다른 방식으로 구현하였다!

 

Lock-Based Queue 의 구현도, 위의 Stack 과 거의 동일하다.

template<typename T>
class LockQueue
{
public:
	LockQueue() { }

	LockQueue(const LockQueue&) = delete;
	LockQueue& operator=(const LockQueue&) = delete;

	void Push(T value)
	{
		lock_guard<mutex> lock(_mutex);
		_queue.push(std::move(value));
		_condVar.notify_one();
	}

	bool TryPop(T& value)
	{
		lock_guard<mutex> lock(_mutex);
		if (_queue.empty())
			return false;

		value = std::move(_queue.front());
		_queue.pop();
		return true;
	}

	void WaitPop(T& value)
	{
		unique_lock<mutex> lock(_mutex);
		_condVar.wait(lock, [this] { return _queue.empty() == false; });
		value = std::move(_queue.front());
		_queue.pop();
	}

private:
	queue<T> _queue;
	mutex _mutex;
	condition_variable _condVar;
};

 

위의 자료구조를 활용하면, main 함수에서는 싱글쓰레드 환경처럼 자료구조를 사용할 수 있을 것이다 😉

LockQueue<int32> q;
LockStack<int32> s;

void Push()
{
	while (true)
	{
		int32 value = rand() % 100;
		q.Push(value);

		this_thread::sleep_for(10ms);
	}
}

void Pop()
{
	while (true)
	{
		int32 data = 0;
		if (q.TryPop(OUT data))
			cout << data << endl;
	}
}


int main()
{
	thread t1(Push);
	thread t2(Pop);
	thread t3(Pop);

	t1.join();
	t2.join();
	t3.join();
}
Comments