상세 컨텐츠

본문 제목

ThreadManager

똑똑한 개발/C++ 게임개발

by 성댕쓰 2021. 9. 1. 21:19

본문

ThreadManager가 하는 역할은 server에서 사용하는 쓰레드 자체, TLS 를 관리하기 위함이다.

ThreadManager 생성하기 앞서 여러 곳에 유용하게 쓰일 수 있는 유틸 코드를 정의해보자.

 

먼저 app을 crash, assert하는 define이다.

CoreMacro.h

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

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

 

__analysis_assume은 파라미터가 참이라고 가정하라는 명령이다.

 

Type 관련 정의도 몇 가지 추가한다.

Types.h

#pragma once
#include <mutex>
#include <atomic>

using BYTE = unsigned char;
using int8 = __int8;
using int16 = __int16;
using int32 = __int32;
using int64 = __int64;
using uint8 = unsigned __int8;
using uint16 = unsigned __int16;
using uint32 = unsigned __int32;
using uint64 = unsigned __int64;

template<typename T>
using Atomic = std::atomic<T>;
using Mutex = std::mutex;
using CondVar = std::condition_variable;
using UniqueLock = std::unique_lock<std::mutex>;
using LockGuard = std::lock_guard<std::mutex>;

굳이 Atomic을 std::atomic<T> 대신 사용하는 이유는 오른쪽 항만 바꾸면 Atomic을 쓰는 모든 코드를 한꺼번에 바꿀 수 있기 때문이다.

 

ThreadManager는 여러 쓰레드를 관리하고 있고 이들의 시작, 종료를 관리한다.

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;

};

 

CoreTLS.h, CoreTLS.cpp

#pragma once

extern thread_local uint32 LThreadId;
#include "pch.h"
#include "CoreTLS.h"

thread_local uint32 LThreadId = 0;

 

 

ThreadManager.cpp

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

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()
{
}

Launch메서드를 보면 callback 실행 전 후에 TLS초기화, 정리 코드가 있다. InitTLS에서는 thread별 ID를 부여한다.

 

ThreadManager를 생성하는 클래스를 하나 둔다.

CoreGlobal.h, CoreGlobal.cpp

#pragma once

extern class ThreadManager* GThreadManager;

class CoreGlobal
{
public:
	CoreGlobal();
	~CoreGlobal();
};
#include "pch.h"
#include "CoreGlobal.h"
#include "ThreadManager.h"

ThreadManager* GThreadManager = nullptr;

CoreGlobal::CoreGlobal()
{
	GThreadManager = new ThreadManager();
}

CoreGlobal::~CoreGlobal()
{
	delete GThreadManager;
}

 

 

사용은 아래와 같이 하면 된다.

GameServer.cpp

#include "pch.h"
#include "ThreadManager.h"

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();
}

만약 컨텐츠 부분에서 엔진쪽 객체를 만드는 게 마음에 들지 않는다면 아래와 같이 바꿔쓴다.

CoreGlobal.h, CoreGlobal.cpp

#pragma once

extern class ThreadManager* GThreadManager;
#include "pch.h"
#include "CoreGlobal.h"
#include "ThreadManager.h"

ThreadManager* GThreadManager = nullptr;

class CoreGlobal
{
public:
	CoreGlobal()
	{
		GThreadManager = new ThreadManager();
	}
	~CoreGlobal()
	{
		delete GThreadManager;
	}
} GCoreGlobal;

 

GameServer.cpp

#include "pch.h"
#include "ThreadManager.h"

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. AssertCrash 함수 사용 이유와 동작 순서는?

- 파라미터가 참인지 아닌지 확인하고 크래시 내기 위해 사

- __analysis_assume은 컴파일러가 오류로 인식하는 것을 방지하기 위해 사용한 것.

2. InitTLS가 하는 역할은?

- ThreadManager를 통해서 동작하는 thread에 id를 발급하기 위해서 사용.

- fetch_add는 변수를 더하고 결과 값을 반환함.

 

 

참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)

'똑똑한 개발 > C++ 게임개발' 카테고리의 다른 글

Deadlock 탐지  (0) 2021.09.06
Read-Writer lock  (0) 2021.09.03
Lock free stack #3  (0) 2021.08.31
Lock free stack #2  (0) 2021.08.26
Lock free stack  (0) 2021.08.25

관련글 더보기

댓글 영역