COM, ATL

TLS(Thread Local Storage)

디버그정 2008. 7. 30. 18:49

TLS(Thread Local Storage)


1. 요약

TLS(Thread Local Storage)는 스레드 별로 고유한 저장공간을 가질 수 있는 방법입니다.


2. 본문

각각의 스레드는 고유한 스택을 갖기 때문에 스택 변수( 지역 변수)는 스레드 별로 고유합니다. 예를 들어서 각각의 스레드가 같은 함수를 실행한다고 해도 그 함수에서 정의된 지역변수는 실제로 서로 다른 메모리 공간에 위치한다는 의미입니다. 그러나 정적 변수나 전역 변수의 경우에는 프로세스 내의 모든 스레드에 의해서 공유됩니다. 이 역시 예를 들고 싶지만, 다 이해하셨으리라 믿고 생략하겠습니다.

그렇습니다. TLS는 정적, 전역 변수를 각각의 스레드에게 독립적으로 만들어 주고 싶을 때 사용하는 것입니다. 다시 말해서, 분명히 같은 문장(context)을 실행하고 있지만 실제로는 스레드 별로 다른 주소공간을 상대로 작업하는 것입니다.

TLS를 사용하는 방법은 2가지가 있습니다. 한 가지는 API에서 지원해주는 방식을 사용하는 것이고, 다른 하는 compiler에서 지원하는 방식을 사용하는 것입니다.

우선은 간단하면서도 신기한 두 번째 방법부터 살펴보겠습니다.

다음과 같이 선언된 전역변수가 있다고 가정해 봅시다.. ( 그럽시다..)

int nWindows;
이 변수는 모든 스레드에 의해서 공유됩니다.
그게 싫다면 다음과 같이 고쳐주시면 됩니다.
_declspec( thread ) int nWindows;
이제 이 변수는 모든 스레드에게 고유한(private) 변수가 되었습니다( 짝짝짝!!)

다음으로는 API를 사용한 방법을 알아봅시다.

DWORD dwIamIndex = ::TlsAlloc();  // 공간 확보 

::TlsSetValue( dwIamIndex, pMyData);  // 데이터 저장 

BYTE* pGiveMe = (BYTE*)::TlsGetValue( dwIamIndex);  // 데이터 얻기 

::TlsFree( dwIamIndex);  // 공간 해제 

자세한 부분은 역시 MSDN을 참조하셔야 겠지요..

한눈에 아실 수 있지만 storage-class modifier를 사용하는 방식이 훨씬 사용하기 쉽습니다.그러나 storage-class modifier를 사용하는 방식에는약간의 제약이 있습니다. 여기서 제약이란, 동적으로 로드되는 DLL에서는 사용될 수 없슴을 의미합니다. 아주 구체적인 속사정을 알 수 없지만, 어쨌든 전역변수를 위한 공간을 마련하는 데 많은 곤란이 있을 것 같지 않습니까? 동적으로 로드되는 DLL 에서는 API 타입을 사용하시는 수 밖에 없습니다.

마지막으로, 어떻게 스레드마다 고유한 공간을 유지할 수 있는 지 신기하지 않습니까?? 기회가 닿으면 다음 시간에 알아보도록 하겠습니다.


3. 예제 코드



4. 참소

Win32 Network Programming – Addison Wesley

위의 내용중에 Dynamic Loaded DLL 에서 Static TLS technic 을 사용하면 안된다고 했는데 그에 대한 설명이 부족한것 같아서 몇자 적어봅니다.

우선 API를 사용하는 방식이 Dynamic 한 방법이고, __declspec(thread)를 사용하는 방법이 static 한 방법이라고 생각할 수 있습니다. 그러면 __declspec(thread)를 사용하여 선언한 변수는 어디에서 관리 되는 걸까요? 우선 static TLS 방식을 사용하면 컴파일러는 자신만의 '.tls' 라는 메모리 section 에 선언된 변수를 등록시킵니다. 물론 이런 각각의 '.tls' section을 다루는 최상위 '.tls' section 이 따로 있습니다. 또한 하나의 프로그램이 또 다른 thread를 생성할 경우, 자신이 가지고 있는 '.tls' section 을 자식 thread 에 복사하는 방법을 사용하고 있습니다.바로 이점 때문에 문제가 발생한다고 보실 수 있습니다. 겉에서 보기에는 여러가지 thread 가 '.tls' section 을 공유하는것 처럼 보이지만 실제로는 내부에서 복사하는 방식을 사용하고 있기 때문이죠. 자.. 부가 설명은 여기까지 하고 제가 다루기로 했던 내용들을 간단한 예를 통해서 좀더 자세히 다루어 보기로 하겠습니다.

우선 A라는 프로그램이 있습니다. A 또한 자신만의 '.tls' section 을 가지고 있습니다. 그리고 A는 5개의 thread를 생성하였습니다. 그럼 A에서 사용하던 '.tls' section 이 모두 6개가 되었습니다. ( A + 5 thread ) 이러한 상황에서 A가 static TLS 를 가지고 있는 DLL 을 LoadLibrary()를 사용하여 Load 하였습니다. 그러면 A는 자신의 '.tls' section 을 늘려서 새로운 DLL 이 가지고 있는 static TLS 를 추가해주어야 합니다. 물론 A는 자신의 '.tls' section 뿐만 아니라 5개의 자식 thread 의 '.tls' section 까지 늘려주어야 합니다. 그리고 나서, 다시 A가 동적으로 load 했던 DLL을 FreeLibrary()를 통해서 메모리로부터 제거하였습니다. 그럼 A는 자신의 '.tls' section 에서 필요없는 DLL의 '.tls' section 을 제거 해야겠죠? section 영역도 줄여야 겠죠. 물론 이와 같은 작업을 5개의 자식 thread의 '.tls' section 에도 해주어야 합니다. 이와 같은 작업을 system 이 하게 되면, 여러가지 문제가 발생하게 됩니다.(초기화가 제대로 이루어 지지 않는 문제 혹은 access violation 등)

결국 이런 이유로 동적으로 DLL 을 Load 하기 위해서 만들어진 DLL은 static TLS 를 사용하는 대신 DLL 스스로가 자신의 TLS section 을 관리할 수 있는 Dynamic TLS 방식을 사용하는 것이 좋습니다.



- 2001.08.19 Smile Seo -