프로젝트 파일
(앞으로 이 코드에 더 추가할 예정.)
https://github.com/RuuNee/IOCP_Game_Server.git
스레드를 std::thread t1; 이런 식으로 만들어서 사용해도 문제가 될건 없지만,
나중에 가면 tls에도 굉장히 많은 데이터도 들어가게 될 것인데 이를 묶어서 관리해줄 코드를 만들어 보자.
Types.h
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>;
우선 자주 사용하는 구조체와 클래스들을 재정의 해주자.
이렇게 하면 나중에 코드 수정하기도 편하다. 그리고 using을 사용하게 되면 template을 대상으로도 작동한다.
CoreGloabl.h, CoreGloabl.cpp
//CoreGloab.h
#pragma once
extern class ThreadManager* GThreadManager;
class CoreGlobal
{
public:
CoreGlobal();
~CoreGlobal();
};
CoreGlobal 클래스는 전역으로 사용할 매니저류의 클래스들을 모아서 사용하게 되는 클래스다.
//CoreGloab.cpp
#include "pch.h"
#include "CoreGlobal.h"
#include "ThreadManager.h"
ThreadManager* GThreadManager = nullptr;
CoreGlobal::CoreGlobal()
{
GThreadManager = new ThreadManager();
}
CoreGlobal::~CoreGlobal()
{
delete GThreadManager;
}
지금은 매니저가 ThreadManager 하나 밖에 없지만 나중에는 매니저가 여러개로 늘어날 수 있기 때문에,
이 매니저들의 생성 순서나 삭제 순서를 맞춰주기 위하여 CoreGlobal의 생성자와 소멸자를 이용할 것이다.
(전역으로 CoreGlobal을 하나 만들어주면 생성자에서 알아서 매니저들을 만들어 주게 된다. 지금은 ThreadManager 하나 밖에 없기 때문에 단순 생성과 삭제만 있다.)
CoreMacro.h
#pragma once
/*----------------------
*
* 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); \
} \
}
우리가 사용할 매크로들을 모아 놓은 헤더파일이다.
우선 지금은 인위적으로 크래시를 내는 매크로를 만들어 놓았다.
#define CRASH(cause)
_Analysis_assume_( expr )
expr - true로 평가되는 모든 식입니다.
코드 분석 도구는 함수가 나타나는 지점에서 식 expr 이 나타내는 조건이 true라고 가정합니다. 예를 들어 변수에 할당하여 가 변경될 때까지 expr true로 유지됩니다.
https://learn.microsoft.com/ko-kr/cpp/code-quality/how-to-specify-additional-code-information-by-using-analysis-assume?view=msvc-170
_Analysis_assume_을 통해 crash가 nullptr이 아니라고 가정하여 컴파일러를 통과한 다음 nullptr인 crash에 값을 넣을려고 하는 순간 크래시가 나는 구조이다.
#define ASSERT_CRASH(expr)
expr이 false면 crash를 내게 한다.
CoreTLS.h, CoreTLS.cpp
//CoreTLS.h
#pragma once
extern thread_local uint32 LThreadId;
//CoreTLS.cpp
#include "pch.h"
#include "CoreTLS.h"
thread_local uint32 LThreadId = 0;
CoreTLS는 쓰레드에서 사용할 tls들을 관리하는 클래스이다.
지금 당장은 쓰레드의 ID를 직접 만들어 관리하기 위해 LThredId 하나만 전역으로 추가해주었다.
ThreadManager.h, ThreadManager.cpp
//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는 말 그대로 스레드들을 관리하는 매니저이다.
락을 잡기 위한 Mutex와
스레드 목록을 vector로 만들어 주었다.
void Launch(function<void(void)> callback);
함수를 받아서 스레드를 실행시키는 함수.
void Join();
스레드가 끝날 때 까지 기다려주는 함수
static void InitTLS();
static void DestroyTLS();
매니저를 만드는 핵심 이유 중 하나는 쓰레드를 만듦과 동시에 TLS영역을 초기화 해주고 삭제시켜주기 위해서이다.
//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(thread([=]()
{
InitTLS();
callback();
DestroyTLS();
})));
}
void ThreadManager::Join()
{
for (std::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()
{
}
- 생성자에서는 메인 쓰레드의 TLS초기화를 위하여 InitTLS()를 호출하였다.
- 소멸자에서는 Join()을 호출해서 스레드를 기다리도록 하였다.
- void Launch(function<void(void)> callback)
혹시나 동시다발적으로 실행될 수도 있기 때문에 우선 lock을 잡아준다.
만든 스레드를 vector<thread>에 넣어준다.
{
TLS를 초기화 시킨 후,
람다식으로 스레드를 만들어서 <void(void)>형식의 함수를 받아 스레드를 실행 시켜준다.
그리고 TLS를 삭제 시켜준다.
}
- void Join()
벡터안에 있는 스레드들을 순회하면서 joinable 상태일 경우 join 시켜주고 최종적으로 쓰레드가 들어있는 벡터를
비워준다.
- void InitTLS()
지금은 TLS에 LThreadId 밖에 없기 때문에 LThreadId만 처리해주었다.
Atomic<uint32> 타입으로 SThreadId = 1 이라는 전역변수를 만들어 준 다음에,
LThreadId = SThreadId.fetch_add(1); 를 해주어서 id를 1씩 늘려서 발급해 주었다.
(main 쓰레드의 id가 1이다.)
- void DestroyTLS()
나중에 TLS에 동적으로 생성되는게 있다면 그때 여기서 지워주면 된다.
'스레드 프로그래밍' 카테고리의 다른 글
DeadLock 탐지 (0) | 2023.02.07 |
---|---|
Reader-Writer Lock (0) | 2023.02.02 |
Lock-Free Stack(3) (0) | 2023.01.27 |
Lock-Free Stack(2) (0) | 2023.01.27 |
Lock-Free Stack (1) (0) | 2023.01.26 |