[Server Programming/cpp] TLS 프로파일러
기존의 프로파일러
싱글스레드 학습할때 만든 프로파일러라서 그저 TAG만 같으면 같은 프로파일링 구역으로 묶여서 다른 스레드임에도 구분없이 프로파일링이 되어버렸다. 멀티스레드에 대응할수있는 프로파일러를 고민하다 TLS를 적용해서 구현해보았다.
만약 더 간단하게 하려면, 스레드마다 스레드ID를 태그 앞에 붙이면 구분이 되기 때문에,, 간단하게 싱글 스레드전용 프로파일러라도 멀티스레드에서 사용이 가능하다.
TLS 프로파일러 초기 설계
thread local에 프로파일 데이터를 가지고 있다. 어디서든지 전역함수로 파일 출력 가능해야하기 때문에, thread local에 있는 프로파일 데이터의 포인터를 전역에서 가지고 있게 설계하였다.
inline static std::vector<PROFILE_THREAD_CONTEXT*> allProfile;
inline static thread_local PROFILE_THREAD_CONTEXT profileThreadContext;
inline static thread_local PROFILE_DATA profileData[PROFILE_MAX] = {};
* 이 부분에서 멀티스레드 상황에서 서로 읽고 쓰기 타이밍 이슈로 인한 값 오차는 허용한다.
최초 스레드가 TLS에 접근 시 락걸고 초기화한다.
문제사항
스레드 종료 후 static Profiler::DataOutText(파일 출력) 하는데 데이터 사라짐 이슈!
-> TLS니깐 스레드가 종료되면, 댕글링 포인터 위험!!
그럼 어떻게하지?
- 힙에 유지하기 - TLS에 구조체의 포인터만 가지고, 데이터는 기본 힙에서 관리, 나중에 Profiler::에서 delete해도 되고~
- 기존 방식에서 스레드가 종료될때 스냅샷처럼 TLS의 프로파일 데이터들을 백업시키고 종료절차
굳이 더 복잡한 방식보다는 1번으로 하였다. 전역에 관리위한 벡터를 만들고 거기서 TLS CONTEXT를 가지고, 스레드는 각TLS에 해당 CONTEXT의 포인터만 가지고 접근하도록 하였다.
그리고 전역벡터 소멸시 직접 DELETE해도 좋지만, 스마트포인터를 사용하였다.
ㄴ 당연하지만, 해당 클래스에서의 소유권을 명시적으로 나타내고, DELETE 실수를 방지하기 위해
스레드마다 프로파일러 호출시 CONTEXT를 어떻게 읽을까?
inline static std::vector<std::unique_ptr<PROFILE_THREAD_CONTEXT>> allProfiles;
inline static thread_local int tlsIndex;
...
auto ctx = allProfiles[tlsIndex] // use
- TLS 인덱스를 두어서 매번 전역벡터에 인덱스 접근하기
inline static std::vector<std::shared_ptr<PROFILE_THREAD_CONTEXT>> allProfiles;
inline static thread_local std::shared_ptr<PROFILE_THREAD_CONTEXT> threadContext;
...
threadContext // use
- 전역 벡터를 shared_ptr로 타입을 두고, TLS도 shared_ptr로 사용하기
더 좋은 방법없을까? 원시포인터를 get해서 사용하면 안될까? 원시포인터를 사용하지 않는 이유 중에 하나가 댕글링포인터의 위험인데, 프로파일러와 TLS의 관계를 보면,, 스레드는 전역 관리가 되는 스마트포인터의 생명주기에 포함되는 관계이다.
즉, 원시포인터를 TLS에서 가지고 있고, 사용하다가 TLS가 항상 먼저 삭제되고 나중에 전역 벡터가 삭제될것이 100%이기 때문에 사용해도 타당하다.
inline static std::vector<std::unique_ptr<PROFILE_THREAD_CONTEXT>> allProfiles;
inline static thread_local PROFILE_THREAD_CONTEXT* tlsContext = nullptr;
...
// 생성자에서
auto ctx = std::make_unique<PROFILE_THREAD_CONTEXT>();
...
tlsContext = ctx.get();
std::lock_guard<std::mutex> lock(mtx);
allProfiles.push_back(std::move(ctx));
https://github.com/OneCrazyman/game-server-portfolio/commit/1c23df13e7b4b990ec013fa28674a98280cbab36