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>;
CorePch.h
#pragma once
#include "Types.h"
#include "CoreMacro.h"
#include "CoreTLS.h"
#include "CoreGlobal.h"
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <iostream>
using namespace std;
pch.h
#pragma once
#define WIN32_LEAN_AND_MEAN // 거의 사용되지 않는 내용을 Windows 헤더에서 제외합니다.
#include "CorePch.h"
위와 같이 자주 사용하는 것들을 미리 헤더파일로 만든 후 pch에 추가하면 유용하게 사용할 수 있다.
pch란 precompiled header의 약자로 직역하면 미리 컴파일된 헤더이다.
pch는 변경되지 않을 cpp나 헤더들을 한번 빌드한 변경사항이 없으면 따로 빌드하지 않는 파일이다.
따라서 무겁거나 긴 소스코드나 헤더가 있을 때 효과적으로 컴파일 시간을 줄여줄 수 있다.
CoreMacro.h
#pragma once
//------------------
// Crash
//------------------
#define CRASH(cause) \
{ \
uint32* crash = nullptr; \
/*crash != nullptr 라고 가정해라*/ \
__analysis_assume(crash != nullptr); \
*crash = 0xBADEDCFE; \
}
//조건에 따라 크래시
#define ASSERT_CRASH(expr) \
{ \
if(!expr) \
{ \
CRASH("ASSERT_CRASH"); \
__analysis_assume(expr); \
} \
}
자주 사용될 메크로 함수들을 저장한 헤더파일이다. 앞으로 서버를 구성하는데 필요한 메크로들을 추가할 예정이다.
이번 글에서는 메인 서버에서 스레드를 관리하는 스레드매니저를 큰 틀만 만들 것이다.
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()
{
}
스레드매니저는 Launch에서 함수를 받아 그 함수를 실행하는 스레드를 만들어주는 일을 한다. 나머지 코드들은 전에 배운 내용을 활용한 것들이니 따로 설명은 하지 않는다.
CoreGlobal.h
#pragma once
extern class ThreadManager* GThreadManager;
class CoreGlobal
{
public:
CoreGlobal();
~CoreGlobal();
};
CoreGlobal.cpp
#include "pch.h"
#include "CoreGlobal.h"
#include "ThreadManager.h"
ThreadManager* GThreadManager = nullptr;
// 매니저가 추가될 경우 객체의 생성, 소멸 순서 맞추기
CoreGlobal::CoreGlobal()
{
GThreadManager = new ThreadManager();
}
CoreGlobal::~CoreGlobal()
{
delete GThreadManager;
}
CoreGlobal는 만약 매니저나 다른 객체들이 늘어나게 될 경우 객체의 생성과 소멸 순서를 맞추는 역할을 한다.
Main.cpp
#include "pch.h"
#include "CorePch.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 (int i = 0; i < 5; i++)
{
GThreadManager->Launch(ThreadMain);
}
GThreadManager->Join();
}
간단하게 5개의 스레드를 만들어 잘 작동하는지 확인해보자
정상적으로 잘 작동하는 모습이다. 지금까지 공부했던 내용을 스레드 매니저로 만들어 관리하게 하였다.
메인스레드의 ID가 1번이므로 2~6번까지 출력되는 것을 볼 수 있다.
'서버 > 멀티스레드' 카테고리의 다른 글
17. DeadLock 탐지 (0) | 2024.08.04 |
---|---|
16. Reader-Writer Lock (0) | 2024.08.02 |
14. Lock-Free Stack #2 (0) | 2024.07.30 |
13. Lock-Free Stack #1 (0) | 2024.07.29 |
12. Lock-Based Queue/Stack (0) | 2024.07.26 |