Thread Manager

Date:     Updated:

카테고리:

태그:

인프런에 있는 루키스님의 게임 서버 강의를 듣고 정리한 내용입니다.


Thread Local Storage (TLS)

thread18

Thread Local Storage (TLS)는 멀티스레드 환경에서 각 스레드가 고유한 데이터를 가질 수 있도록 해주는 메커니즘이다. 쓰레드별로 Stack 메모리는 가지고 있지만 스택은 전역 변수, static 변수와 같은 데이터를 다룰 수 없다. 이런 경우를 해결하기 위해 TLS가 필요하다.
TLS를 사용하는 방법은 간단한데, 변수 앞에 thread_local 키워드를 붙여주면 된다. TLS를 사용하는 가장 대표적인 예시는 다음과 같다.

  • 1) 중구 난방인 Thread ID를 1부터 깔끔하게 정리하고 싶은 경우
  • 2) 쓰레드마다 별도의 로그 관리


thread_local std::ostringstream thread_log;

void log_message(const std::string& message) {
    thread_log << message << std::endl;
}

void thread_function(int id) {
    log_message("Thread " + std::to_string(id) + " starting");

    // Do something

    log_message("Thread " + std::to_string(id) + " ending");
    std::cout << thread_log.str();  // 각 스레드의 로그를 출력
}

int main() {
    std::thread t1(thread_function, 1);
    std::thread t2(thread_function, 2);

    t1.join();
    t2.join();

    return 0;
}


Macro

개발을 하다보면 특정 상황에서 크래시를 내고 싶은 상황이 있다. 이런 경우 강제로 크래쉬를 발동시키게 하는데, 애매하게 3/0 같은 연산을 해버리면 크래쉬가 나기 전에 컴파일러 단에서 오류로 잡아버린다.


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

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

Pasted image 20240613031348

  • 오른쪽 끝에 \를 붙여주면 여러 줄의 define 선언 가능
  • __analysis_assume(crash != nullptr) : 컴파일러에게 crash는 null이 아니야 라고 거짓말
  • 해당 조건이 true인지 아닌지를 가지고, 조건부로 crash 발동


Thread Manager

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

Thread Manager 클래스를 정의해 쓰레드들을 관리하면 여러가지 이점이 있다. 예를 들어 소멸자에서 join()함수들을 호출할 수 있고, 동일한 작업을 하는 쓰레드들도 편하게 생성할 수 있다. 하지만 가장 중요한 역할은 각 스레드의 TLS를 관리하는 것이다.


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

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

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

main() 쓰레드에서 쓰레드를 생성하면 보통은 callback 함수만 호출하게 된다. 하지만 위 예시에서는 callback 함수의 호출 전/후로 TLS 데이터를 관리하는 것을 볼 수 있다. 여기에서는 Thread ID를 static 변수로 지정해 하나씩 늘려나가는 데이터를 관리하게 된다.



맨 위로 이동하기

Server 카테고리 내 다른 글 보러가기

댓글 남기기