우리가 사용하던 스레드의 메모리 나타낸 그림이다.
그림에서 보듯이 Heap 영역과 데이터 영역은 공용으로 같이 활용하는 영역이다.
반면에 스택은 각자 따로 분리되어 데이터를 사용하게 된다.
그래서 Heap 영역 또는 데이터 영역에 자신이 사용하려는 데이터가 있을 경우 동시접근을 하기 위해(race condition 방지) lock을 잡아서 동기화를 해서 한번에 한 개의 스레드만 접근하도록 해주었다.
TLS(Thread Local Stroage)
그런데 사실 위의 그림에는 숨겨진 녀석이 있는데 바로 TLS(Thread Local Storage)라는 녀석이다.
대충 스레드마다 가지고 있는 로컬 저장소라는 의미이다.
그래서 Heap 영역 또는 데이터 영역에 자신이 사용하려는 데이터가 있을 경우 동시접근을 하기 위해(race condition 방지) lock을 잡아서 동기화를 해서 한번에 한 개의 스레드만 접근하도록 해주었다.
위와 같은 상황에서 데이터를 하나씩 꺼내 사용하다보면 데이터를 대상으로 경합이 심해지게 되면서 어떤 스레드는 일을 하지 못하고 대기만 하게 될것이다.
그러니 데이터를 하나씩 가져오지 말고 자기가 사용할 데이터를 충분히 큼지막하게 TLS영역으로 끌고와서 자기가 사용한다면, 처음 큼지막하게 가져오는 동안만 lock을 잡아놓으면 더 이상의 경합은 발생하지 않게 될 것이다. (왜냐하면 TLS영역 자체가 TLS영역을 소유하고 있는 스레드만 접근할 수 있는 영역이기 때문이다.)
그런데 TLS를 보다보니 본인만 접근할 수 있다는게 기존의 스택 메모리와 유사하게 느껴진다.
스택 메모리
스택은 함수를 위한 메모리 공간이고 함수가 끝나고 스택 프레임이 정리가 되면서 메모리 일부분이 유효하지 않게 바뀌는 불안정한 메모리이기 때문에 스택에 저장하는건 위험하다고 한다.
TLS(Thread Local Storage)
TLS같은 경우는 일반적인 전역 변수를 사용하는 것과 마찬가지로, 전역변수를 사용하듯이 TLS를 사용할 수 있는데,
전역이긴 하지만 다른 스레드는 사용할 수 없는 나만의 전역변수 느낌이다.
thread_local int32 LThreadId = 0;
void ThreadMain(int32 threadid)
{
LThreadId = threadid;
while (true)
{
cout << "(" << LThreadId << ")번 째 스레드" << endl;
this_thread::sleep_for(1s);
}
}
int main()
{
vector<thread> threads;
for (int32 i = 0; i < 10; i++)
{
int32 threadId = i + 1;
threads.push_back(thread(ThreadMain, threadId));
}
for (thread& t : threads)
t.join();
}
예제 코드이다.
LThreadId라는 변수가 TLS가 아니라면 전역으로 쓰이는 변수이다 보니 다른 스레드가 사용하게되면 기존 값이 덮어 씌워지면서 기존 값이 날라가게 되겠지만 TLS로 만드는 순간 각 스레드 마다 자신의 공간을 가지게 되기 때문에 정상적으로 id가 입력되게 된다.
'스레드 프로그래밍' 카테고리의 다른 글
Lock-Free Stack (1) (0) | 2023.01.26 |
---|---|
lock stack,queue (0) | 2023.01.26 |
Future (0) | 2023.01.05 |
조건 변수(Condition Variable) (0) | 2023.01.05 |
Event (0) | 2023.01.05 |