본문 바로가기
컴퓨터/Server

Thread Local Storage

by 김짱쌤 2015. 4. 26.

Thread Local Storage

멀티 쓰레드 프로그래밍을 하다보면 불편한게 있다. 쓰레드별 고유한 전역변수(또는 정적변수) 사용하기가 어렵다는 것. 쓰레드를 그냥 만들면 쓰레드에게 주어진 혼자만의 공간은 지역적인 stack영역 뿐이다. 일반적인 전역변수는 다 공유되는 data영역에 저장된다. 그렇다고 힙에 만들어도 private 힙이 아닌 이상 역시나 공유되는 공간이다. 그렇다고 int하나를 위해서 private 힙을 만드는 것은 말이 안된다. 스레드가 특정 함수만을 반복적으로 수행하면 별로 필요없다고 생각할 수 도 있는데, 막상 없으면 아쉽고 쓸려면 없다. Thread Local Storage(이하 TLS)는 쉽게(?) 쓰레드별 저장공간을 마련해주는 방법이다.

TLS의 원리

https://msdn.microsoft.com/en-us/library/ms686749(v=vs.85).aspx

위 그림이 TLS의 원리에 대해서 정말 잘 설명하고 있다. TLS는 말그대로 쓰레드 별 저장공간이다. 그 저장 공간은 index로 구성된 배열(그림 상)이다. 프로그래머는 자신의 코드에서 전역변수처럼 TLS의 인덱스를 사용한다(gdwTlsIndex). 코드에서 모든 쓰레드가 같은 전역 변수를 쓰는 것처럼 보이겠지만, 해당 인덱스에 들어가 있는 데이터는 쓰레드 별로 다르기 때문에, 쓰레드 별 독립된 변수를 사용할 수 있게 된다. 코드상으로는 Homogeneous한 멀티 쓰레드 프로그래밍일지라도, TLS를 사용하여 실제 하는일은 서로 다르게 만들 수 있다.

TLS를 쓰는 법 1 : 컴파일러의 도움을 받자.

__declspec(thread) int lDwThreadId;

위 처럼 변수를 선언하면, 컴파일러가 스레드 별로 별도의 메모리(.tls)영역을 할당해서 thread 전용 변수들을 저장한다. 사용방식은 매우 간편하지만 몇가지 단점이 있다. 우선 컴파일할때 TLS의 크기가 결정되기 때문에 정적으로만 사용가능하다. 런타임에 쓰레드에게 더 큰 메모리할당이 필요해도 확장하는 것이 불가능하다. 두번째로 운영체제가 일반적으로 사용하는 메모리와는 다른 별도의 메모리 영역을 사용하기 때문에, 컴파일러의 TLS변수를 참조하는 코드는 번역단계에서 추가적인 명령들이 필요하다. 따라서 속도가 좀 느려진다는 단점이 있다.

TLS를 쓰는 법 2 : API를 사용하자

//TLS 공간 요청 & TLS 인덱스 반환
DWORD dwTlsIndex = ::TlsAlloc();

//TLS 인덱스에 데이터 저장
::TlsSetValue(dwTlsIndex, pMyData);

//TLS 인덱스의 데이터 불러오기
BYTE* pData = (BYTE*)::TlsGetValue(dwTlsIndex);

//TLS 공간 해제
::TlsFree(dwTlsIndex);

내부적으로 예약된 TLS 공간을 사용하는 방법이다. 사용할 수 있는 영역의 Index를 받아서 BYTE단위로 원하는 데이터를 save/load 할 수 있다. 필요할 때마다 TlsAlloc 명령어를 통해서 원하는 공간을 요청할 수 있다는 점에서 동적이다. 그리고 기존 메모리 구조를 그대로 사용(하는 것 같다)해서 별도의 추가 명령어가 필요하지 않다. 사용이 다소 복잡해 보이긴 하지만, 효율을 원한다면 API의 TLS를 사용하는 것이 좋아 보인다.

TLS를 사용하기 경우

일단 _beginthreadEx를 사용해서 쓰레드를 생성했다면 님은 이미 TLS를 사용중이다. 지금까지 언급했던 멀티쓰레드 프로그래밍의 자원 동기화 이슈를 생각해보면 공기처럼 사용하던 GetLastError 함수의 이상한 점을 발견할 수 있을 것이다. 어떻게 발생한 마지막 에러메시지를 띄워주는 이 함수가 thread safe하게 동작했던 것인가! 답은 물론 TLS다. 마지막 error메시지를 TLS영역에 저장하여 쓰레드가 GetLastError를 요청하면 정해진 인덱스로부터 값을 읽어온다.

이외에도 싱글 쓰레드 작업에서 전역으로 사용하던 변수를 멀티 쓰레드에서 TLS를 통해 사용할 수 있다. 그리고 쓰레드를 작업별로 구분하는 flag, 쓰레드별로 관리되어야 하는 객체(타이머)등에도 사용할 수 있다. 사실 쓰기 나름이고, 잘쓰면 한없이 잘사용할 수 있다. 중요한 것은 이렇게 쓸 수 있는 방법이 있다는 것을 아는 것이라고 생각한다.

'컴퓨터 > Server' 카테고리의 다른 글

멀티쓰레드 서버 버그잡기  (0) 2015.05.04
Lock Free  (2) 2015.04.28
read-write lock  (2) 2015.04.26
DeadLock  (0) 2015.04.25
Pooling  (0) 2015.04.17