TLS(Thread Local Storage)
C++/C++ 2021. 6. 27. 22:05 |TLS(Thread Local Storage)
스레드 별로 독립적인 할당된 메모리 공간을 말한다.
즉, 자기 스레드에 한하여 전역으로 접근할 수 있음을 보장한다.
Rules & Limitations
thread_local 속성은 다음 경우에 적용할 수 있다.
- class declarations & definitions
- data declarations & definitions
thread_local 지시어는 static 속성의 데이터 항목에만 적용할 수 있다.
- global data objects( static 이나 extern )
- local static objects
- static data members of class
TLS는 언제 필요할까?
1) thread safe함을 보장하기 위해?
예를들어 Thread_1에서 어떤 함수 FuncA를 실행한다고 가정해보자.
이때 다른 여러 스레드들도 함수 FuncA를 실행한다고 가정해보자.
어떤 스레드가 어떻게 하던간에 각 스레드에서 실행되는 FuncA의 로컬 변수는 완벽하게 thread safe가 보장된다.
다른 스레드의 스택은 건드릴 수가 없기 때문이다.
그러므로 단순히 thread safe함을 보장하기 위해 TLS를 사용하는 것은 아니다.
2) 전역 접근이 가능하기 때문에?
다음 상황을 가정해보자.
함수 FuncA내부에서 FuncB를 호출하고,
FuncB는 내부에서 FuncC와 FuncD를 호출하는 방식으로 되어있다.
이때 호출된 함수들에서 공통적으로 쓰레드의 ID를 필요로 할 경우 어떻게 해야할까?
첫번째 방법 :
- 최초의 함수 FuncA에서 쓰레드 ID를 얻어서 FuncB에 넘겨주고,
FuncB는 넘겨받은 쓰레드 ID를 FuncC에게
FuncC는 넘겨받은 쓰레드 ID를 FuncD에게 넘겨주게 될 것이다.
( 서로 다른 함수끼리는 서로의 로컬 변수를 알수 없기 때문 )
int FuncC(unsigned int id) {
if (id != 0) {
// ...
return 1;
}
else {
// ...
return 0;
}
}
int FuncB(unsigned int id) {
if (id != 0) {
// ...
return FuncC(id);
}
else {
// ...
return 0;
}
}
int FuncA() {
auto id = (unsigned int)std::this_thread::get_id;
int a = FuncB(id);
// ...
return a != 0 ? a : id;
}
int main() {
std::vector<std::thread> threads(50);
for (size_t i = 0; i < 50; i++)
{
threads.push_back(std::forward<std::thread>(std::thread(FuncA)));
}
for (auto& temp : threads) {
if (temp.joinable())
temp.join();
}
}
두번째 방법 :
- 각 함수마다 쓰레드의 ID를 받아오는 함수를 호출해야한다.
int FuncC() {
auto id = (unsigned int)std::this_thread::get_id;
if (id != 0) {
// ...
return 1;
}
else {
// ...
return 0;
}
}
int FuncB() {
auto id = (unsigned int)std::this_thread::get_id;
if (id != 0) {
// ...
return FuncC(id);
}
else {
// ...
return 0;
}
}
int FuncA() {
auto id = (unsigned int)std::this_thread::get_id;
int a = FuncB();
// ...
return a != id ? a : id;
}
int main() {
std::vector<std::thread> threads(50);
for (size_t i = 0; i < 50; i++)
{
threads.push_back(std::forward<std::thread>(std::thread(FuncA)));
}
for (auto& temp : threads) {
if (temp.joinable())
temp.join();
}
}
첫번째 방법과 두번째 방법 모두 비효율적이고 코드가 늘어난다.
세번째 방법 :
- 이때 TLS를 사용한다면 스레드 전역에서 접근이 가능하다.
즉, TLS는 쓰레드 마다 따로 제공되는 전역변수와 같기 때문에 어디서든지 접근이 가능하다.
thread_local unsigned int tls_thread_id= 0;
int FuncC() {
auto id = tls_thread_id;
if (id != 0) {
// ...
return 1;
}
else {
// ...
return 0;
}
}
int FuncB() {
auto id = tls_thread_id;
if (id != 0) {
// ...
return FuncC();
}
else {
// ...
return 0;
}
}
int FuncA() {
if (tls_thread_id == 0)
tls_thread_id = (unsigned int)std::this_thread::get_id;
auto id = tls_thread_id;
int a = FuncB();
//...
return a != id ? a : id;
}
FuncA호출 하기도 전에서 외부에서 tls_thread_id변수에 쓰레드 ID받아왔다면
FuncA함수 내부에서 쓰레드 ID를 받아오는 API를 호출할 필요도 없어진다.
TLS 사용법
예전에는 운영체제마다 TLS를 생성하는 방법이 달랐다.
아래 방법은 windows에서 WINAPI를 이용하여 TLS를 생성하는 방법이다.
__declspec(thread) int32 value;// Thread-Local Storage 생성 방법( Legacy )
이제는 C++ 표준에 TLS가 정립되어 통일된 방법으로 사용할 수 있게 되었다.
thread_local 타입 변수명;
예시)
thread_local std::queue<int32> q;
사용 예시
thread_local int32 LThreadId;//c++표준에 의거한 TLS 생성 방법
void ThreadMain(int32 threadId) {
LThreadId = threadId;
while (true) {
std::cout << "Hello ! I am Thread # " << LThreadId << std::endl;
std::this_thread::sleep_for(1s);
}
}
int main() {
std::vector<std::thread> threads;
for (size_t i = 0; i < 10; i++)
{
int32 threadId = i + 1;
threads.push_back(std::thread(ThreadMain, threadId));
}
for(auto& t: threads)
t.join();
}
// 헤더파일
extern thread_local int thread_id;
// 소스파일
thread_local int thread_id=0;
'C++ > C++' 카테고리의 다른 글
constexpr, consteval, constinit 키워드 & Static Initialization Order Fiasco (0) | 2021.07.14 |
---|---|
[exception specifier] noexcept (0) | 2021.07.11 |
Effective C++ Operator = 에서는 자기 대입에 대한 처리가 빠지지 않도록 하라 (0) | 2021.06.27 |
Effective C++ 객체 생성 및 소멸 과정 중에는 가상 함수를 호출하지 말라 (0) | 2021.06.27 |
Effective C++ 컴파일러가 만든 함수가 필요 없는 경우 이를 제거하라 (0) | 2021.06.27 |