[2026] C++ thread_local | 스레드 로컬 저장소 가이드
이 글의 핵심
C++ thread_local의 C++, thread_local, 스레드, 1.
들어가며
C++11의 thread_local은 각 스레드마다 독립적인 저장소를 제공하여 스레드 안전한 코드를 작성할 수 있게 합니다. 멀티스레드 환경에서 동기화 없이 스레드별 데이터를 관리할 수 있습니다.
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
1. thread_local 기본
개념
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <iostream>
thread_local int counter = 0;
void func() {
counter++;
std::cout << "스레드 " << std::this_thread::get_id()
<< ": " << counter << std::endl;
}
int main() {
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
}
기본 사용
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <iostream>
thread_local int x = 0;
void worker() {
x++;
std::cout << "스레드 " << std::this_thread::get_id()
<< ": " << x << std::endl;
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
}
2. 실전 예제
예제 1: 스레드별 카운터
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <vector>
#include <iostream>
thread_local size_t requestCount = 0;
void handleRequest() {
requestCount++;
std::cout << "스레드 " << std::this_thread::get_id()
<< " 요청: " << requestCount << std::endl;
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; i++) {
threads.emplace_back( {
for (int j = 0; j < 3; j++) {
handleRequest();
}
});
}
for (auto& t : threads) {
t.join();
}
}
예제 2: 스레드별 버퍼
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <vector>
#include <iostream>
thread_local std::vector<int> buffer;
void flush(const std::vector<int>& buf) {
std::cout << "Flush: " << buf.size() << " items" << std::endl;
}
void process(int value) {
buffer.push_back(value);
if (buffer.size() >= 100) {
flush(buffer);
buffer.clear();
}
}
int main() {
std::thread t1( {
for (int i = 0; i < 150; i++) {
process(i);
}
});
t1.join();
}
예제 3: 난수 생성기
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <random>
#include <thread>
#include <iostream>
thread_local std::mt19937 rng(std::random_device{}());
int getRandomNumber() {
std::uniform_int_distribution<int> dist(1, 100);
return dist(rng);
}
int main() {
std::thread t1( {
for (int i = 0; i < 5; i++) {
std::cout << "스레드 1: " << getRandomNumber() << std::endl;
}
});
std::thread t2( {
for (int i = 0; i < 5; i++) {
std::cout << "스레드 2: " << getRandomNumber() << std::endl;
}
});
t1.join();
t2.join();
}
3. 초기화
스레드 시작 시
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <iostream>
thread_local int x = 10;
void worker() {
std::cout << "x = " << x << std::endl;
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
}
첫 사용 시
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <iostream>
int compute() {
std::cout << "compute() 호출" << std::endl;
return 42;
}
void func() {
thread_local int y = compute();
std::cout << "y = " << y << std::endl;
}
int main() {
std::thread t1( {
func();
func();
});
t1.join();
}
4. 자주 발생하는 문제
문제 1: 소멸 순서
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <thread>
#include <iostream>
struct Resource {
~Resource() {
std::cout << "Resource 소멸" << std::endl;
}
};
thread_local Resource r;
void func() {
std::cout << "func() 실행" << std::endl;
}
int main() {
std::thread t1(func);
t1.join();
}
문제 2: 클래스 멤버
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
class MyClass {
public:
static thread_local int x;
};
thread_local int MyClass::x = 0;
int main() {
MyClass::x = 42;
std::cout << MyClass::x << std::endl; // 42
}
문제 3: 초기화 비용
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <memory>
#include <iostream>
struct ExpensiveObject {
ExpensiveObject() {
std::cout << "ExpensiveObject 생성" << std::endl;
}
};
thread_local std::unique_ptr<ExpensiveObject> obj;
void func() {
if (!obj) {
obj = std::make_unique<ExpensiveObject>();
}
}
int main() {
func();
func();
}
문제 4: 메모리 사용
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <vector>
#include <thread>
#include <iostream>
thread_local std::vector<int> largeBuffer(1000000);
void worker() {
std::cout << "Buffer size: " << largeBuffer.size() << std::endl;
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
}
5. 사용 패턴
패턴 1: 스레드별 캐시
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <unordered_map>
#include <string>
thread_local std::unordered_map<std::string, int> cache;
int getValue(const std::string& key) {
if (cache.find(key) != cache.end()) {
return cache[key];
}
int value = computeValue(key);
cache[key] = value;
return value;
}
패턴 2: 스레드별 통계
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
struct Statistics {
size_t count = 0;
size_t errors = 0;
void print() {
std::cout << "Count: " << count << ", Errors: " << errors << std::endl;
}
};
thread_local Statistics stats;
void processRequest() {
stats.count++;
}
정리
핵심 요약
- thread_local: 스레드별 독립 변수
- 초기화: 스레드 시작 또는 첫 사용 시
- 용도: 캐시, 통계, 난수 생성기
- 성능: 접근 빠름, 초기화 비용 있음
- 메모리: 스레드 수 × 변수 크기
thread_local vs 전역 변수
| 특성 | thread_local | 전역 변수 |
|---|---|---|
| 스레드 안전 | O | X |
| 동기화 필요 | X | O |
| 메모리 | 스레드당 | 1개 |
| 성능 | 빠름 | 동기화 필요 시 느림 |
실전 팁
- 스레드별 캐시에 활용
- 난수 생성기는 thread_local 사용
- 초기화 비용 고려
- 메모리 사용량 주의
다음 단계
- C++ jthread
- C++ random_device
- C++ Mutex