43. thread_local 저장 기간의 초기화·소멸 시점과 순서
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C++
// [주제] thread_local 저장 기간의 초기화·소멸 시점과 순서
//
// 게임서버는 워커 스레드별 로그 버퍼, 스레드 로컬 오브젝트 풀, 스레드 로컬 난수 엔진처럼
// thread_local 을 광범위하게 쓴다. 아래 프로그램은 그 수명(lifetime)을 추적한다.
//
// [과제]
// 1) 이 프로그램의 표준 출력이 어떤 순서로 찍힐지 예측하라.
// 특히 다음을 각각 근거와 함께 답하라.
// - g_tlNamespace 는 언제, 어느 스레드에서 몇 번 생성되는가?
// - Worker1 은 LogBuffer 와 ObjectPool 을 어떤 순서로 만들고 어떤 순서로 파괴하는가?
// - Worker2 는 Worker1 과 같은 개수의 thread_local 을 만드는가?
// - main 스레드의 thread_local 과 g_staticGlobal 중 어느 쪽이 먼저 파괴되는가?
// 2) 함수 로컬 thread_local 의 동적 초기화는 언제 일어나며,
// 매 호출마다 컴파일러가 무엇을 검사하는가? 그 비용은 얼마인가?
// 3) 스레드 종료 시 thread_local 소멸자가 호출되는 순서를 결정하는 규칙은 무엇인가?
// 4) DumpOnExit 의 소멸자는 안전한가? 왜 그렇게 판단했는지 설명하라.
// 5) RunPooledTask 가 같은 스레드에서 두 번 호출될 때 출력이 왜 그렇게 나오는지 설명하라.
// 6) LeakPointer 가 반환한 포인터를 다른 스레드에서 쓰면 무슨 일이 생기는가?
#include <iostream>
#include <random>
#include <string>
#include <thread>
#include <vector>
struct Tracer {
std::string name;
explicit Tracer(std::string n) : name(std::move(n)) {
std::cout << " [ctor] " << name << '\n';
}
~Tracer() { std::cout << " [dtor] " << name << '\n'; }
void Touch() const {}
};
// ── 정적 저장 기간 ────────────────────────────────────────────────────────
Tracer g_staticGlobal("static : g_staticGlobal");
// ── 네임스페이스 스코프 thread_local ──────────────────────────────────────
thread_local Tracer g_tlNamespace("tl:ns : g_tlNamespace");
// ── 함수 로컬 thread_local ────────────────────────────────────────────────
Tracer& LogBuffer() {
static thread_local Tracer buf("tl:func : LogBuffer");
return buf;
}
Tracer& ObjectPool() {
thread_local Tracer pool("tl:func : ObjectPool");
return pool;
}
// 소멸자에서 다른 thread_local 을 만진다
struct DumpOnExit {
~DumpOnExit() {
std::cout << " [dtor] tl:func : DumpOnExit -> touching LogBuffer\n";
LogBuffer().Touch();
}
};
DumpOnExit& Dumper() {
thread_local DumpOnExit d;
return d;
}
// ── 스레드 로컬 상태의 재사용 ─────────────────────────────────────────────
int& RequestCounter() {
thread_local int counter = 0;
return counter;
}
std::mt19937& Rng() {
thread_local std::mt19937 engine(12345);
return engine;
}
void RunPooledTask(const char* taskName) {
++RequestCounter();
std::cout << " " << taskName << ": counter=" << RequestCounter()
<< " roll=" << (Rng()() % 100) << '\n';
}
// ── thread_local 주소를 밖으로 넘긴다 ─────────────────────────────────────
Tracer* LeakPointer() { return &LogBuffer(); }
// ── 워커 ──────────────────────────────────────────────────────────────────
void Worker1() {
std::cout << " Worker1: begin\n";
ObjectPool().Touch();
LogBuffer().Touch();
Dumper();
g_tlNamespace.Touch();
std::cout << " Worker1: end\n";
}
void Worker2() {
std::cout << " Worker2: begin\n";
LogBuffer().Touch();
RunPooledTask("Worker2/taskA");
RunPooledTask("Worker2/taskB");
std::cout << " Worker2: end\n";
}
int main() {
std::cout << "main: begin\n";
std::cout << "main: spawn Worker1\n";
std::thread(Worker1).join();
std::cout << "main: spawn Worker2\n";
std::thread(Worker2).join();
std::cout << "main: touch LogBuffer on main thread\n";
Tracer* p = LeakPointer();
std::cout << "main: leaked ptr name = " << p->name << '\n';
std::cout << "main: end\n";
return 0;
} 내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.