KoreanFoodie's Study

[C++ 게임 서버] 1-21. ThreadManager 본문

Game Dev/Game Server

[C++ 게임 서버] 1-21. ThreadManager

GoldGiver 2023. 7. 27. 15:12

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

[C++ 게임 서버] 1-21. ThreadManager

핵심 :

1. Thread 를 직접 생성하기보다, ThreadManager 를 통해 생성해 보자.

2.
ThreadManager 를 통해 쓰레드를 관리하면, 초기화 및 종료 시점의 작업을 자동화시킬 수 있을 것이다.

3.
CRASH 관련 매크로를 유용하게 활용하자.

이제 본격적으로 서버 구축을 시작하기 전에, Thread 관리를 도와줄 ThreadManager 를 만들어 보도록 하겠다.

원리나 기능은 간단하다. 현재로서는 Thread Local Storage 에 각 쓰레드별로 ID 를 부여하고, 생성시에 이를 출력하는 정도의 기능만 만들 것이다.

코드를 보면 이해가 빠르다.

 

ThreadManager.h

#pragma once

#include <thread>
#include <functional>

/*------------------
	ThreadManager
-------------------*/

class ThreadManager
{
public:
	ThreadManager();
	~ThreadManager();

	void	Launch(function<void(void)> callback);
	void	Join();

	static void InitTLS();
	static void DestroyTLS();

private:
	Mutex			_lock;
	vector<thread>	_threads;
};

 

ThreadManager.cpp

#include "pch.h"
#include "ThreadManager.h"
#include "CoreTLS.h"
#include "CoreGlobal.h"

/*------------------
	ThreadManager
-------------------*/

ThreadManager::ThreadManager()
{
	// Main Thread
	InitTLS();
}

ThreadManager::~ThreadManager()
{
	Join();
}

void ThreadManager::Launch(function<void(void)> callback)
{
	LockGuard guard(_lock);

	_threads.push_back(thread([=]()
		{
			InitTLS();
			callback();
			DestroyTLS();
		}));
}

void ThreadManager::Join()
{
	for (thread& t : _threads)
	{
		if (t.joinable())
			t.join();
	}
	_threads.clear();
}

void ThreadManager::InitTLS()
{
	static Atomic<uint32> SThreadId = 1;
	LThreadId = SThreadId.fetch_add(1);
}

void ThreadManager::DestroyTLS()
{

}

위에서 CoreTLS 에는 그냥 thread_local 한 변수를 하나 선언만 해 줄 것이다.

Launch 함수를 활용하면, callback 으로 들어가는 함수를 수행하는 쓰레드가 하나 생성되어 쓰레드들을 담는 vector 에 넣어준다.

InitTLS 와 DestroyTLS 는 초기화 및 종료시 정리를 하는 작업을 수행할 것이다.

 

이제 아래와 같이 main 함수에서 ThreadManager 를 활용하면...

CoreGlobal Core;

void ThreadMain()
{
	while (true)
	{
		cout << "Hello ! I am thread... " << LThreadId << endl;
		this_thread::sleep_for(1s);
	}
}

int main()
{
	for (int32 i = 0; i < 5; i++)
	{
		GThreadManager->Launch(ThreadMain);
	}

	GThreadManager->Join();
}

아래와 같이, 1초마다 각 쓰레드별로 Id 값을 출력하는 간단한 동작을 확인할 수 있다 😊

 

 

아, 참고로 앞으로 Crash 관련 이슈를 확인할 매크로를 하나 더 기록한다.

 

CoreMacro.h

/*---------------
	  Crash
---------------*/

#define CRASH(cause)						\
{											\
	uint32* crash = nullptr;				\
	__analysis_assume(crash != nullptr);	\
	*crash = 0xDEADBEEF;					\
}

#define ASSERT_CRASH(expr)			\
{									\
	if (!(expr))					\
	{								\
		CRASH("ASSERT_CRASH");		\
		__analysis_assume(expr);	\
	}								\
}

위에서 CRASH 를 사용하면 일부러 크래시를 낼 수 있고, ASSERT_CRASH 를 사용하면 expr 값에 따라 crash 를 발생시킬 것이다.

참고로, __analysis_assume 의 경우, 인자가 항상 참이라고 컴파일러에게 알려주어 경고를 줄일 수 있다고 한다. 즉, 위에서 __analysis_assume 을 사용한 이유는... 우리가 일부러 크래시를 내는 것을 원하기 때문에, 혹시라도 모를 컴파일러의 오지랖(?)을 막기 위해서 쓴 것이다 🤣

자세한 내용은 이 링크를 참고하자 😉

Comments