TLS란 ? Thread 별로 local에 storage를 가지는 것. ( =Thread 마다의 unique한 저장공간을 의미한다.)
이 이상의 설명(이론)을 보면 더 머리가 복잡해지니 바로 함수를 보는게 더 좋을것같다.
이론 -> 실전 보다는 실전에서 이해하는게 더 좋은듯 (?)
__declspec(thread) 를 붙이는 방법도 있지만 이건 생략한다.
Windows 환경의 TLS는 함수 4개만 알면 쉽고 유용하게 써먹을 수 있다. (TlsAlloc, TlsSetValue, TlsGetValue, TlsFree)
DWORD TlsAlloc(
);
- 파라미터가 없다.
- 실패하면 TLS_OUT_OF_INDEXES (= -1 (0xffffffff) 를 return 한다.)
BOOL TlsSetValue(
DWORD dwTlsIndex,
LPVOID lpTlsValue
);
- TlsAlloc()으로 받은 index를 통해 TLS data를 저장한다.
LPVOID TlsGetValue(
DWORD dwTlsIndex
);
- TlsAlloc()으로 받은 index를 통해 TLS data를 얻어온다.
BOOL TlsFree(
DWORD dwTlsIndex
);
- TlsAlloc()으로 받은 index를 해제한다.
위의 내용은 msdn에 저렇게 설명되어 있지는 않다. 좀 더 내부 원리를 이해할 수 있는 설명이 나와있지만, 원리는 생략!
(자세한 원리는 https://docs.microsoft.com/ko-kr/windows/desktop/ProcThread/thread-local-storage 에 아주 잘 나와있으니 참고)
바로 예제 코드로 넘어가보자.
#include <stdio.h>
#include <windows.h>
typedef struct {
int a;
int b;
int c;
int d;
}tls_data_t;
DWORD tls_index;
int init_tls_data(int a)
{
tls_data_t *pData = NULL;
pData = (tls_data_t *)TlsGetValue(tls_index);
if(pData == NULL) {
printf("[test_thread(%d)] pData is NULL!\n", a);
pData = (tls_data_t *)malloc(sizeof(tls_data_t));
memset(pData, 0, sizeof(tls_data_t));
}
pData->a += a;
pData->b += a;
pData->c += a;
pData->d += a;
TlsSetValue(tls_index, (LPVOID)pData);
return 0;
}
DWORD WINAPI test_thread(LPVOID arg)
{
int a = (int)arg;
int i=0;
tls_data_t *pData = NULL;
init_tls_data(a);
pData = (tls_data_t *)TlsGetValue(tls_index);
if (pData) {
printf("[test_thread(%d)][tls_index:%ld] Data >> a:%d b:%d c:%d d:%d\n", a, tls_index,
pData->a, pData->b, pData->c, pData->d);
free(pData);
pData = NULL;
}
return 0;
}
const int MAX_THREAD_NUM=10;
int main()
{
HANDLE hThread[MAX_THREAD_NUM];
DWORD ThreadId[MAX_THREAD_NUM];
int i;
tls_index = TlsAlloc();
for(i=0;i<MAX_THREAD_NUM;i++) {
hThread[i] = CreateThread(NULL, 0, test_thread, i, 0, &ThreadId[i]);
}
WaitForMultipleObjects(MAX_THREAD_NUM, hThread, TRUE, INFINITE);
TlsFree(tls_index);
return 0;
}
실행 결과는 직접 확인하는걸로.
코드를 글로 풀이하자면
- 전역변수 tls_index에 TlsAlloc() 을 통해 할당받은 index를 저장
- thread 들을 실행
- TlsGetValue() 함수를 통해 값을 얻어오고 값이 없으면 동적 할당
- 동적 할당한 영역에 데이터를 저장하고 TlsSetValue()로 저장
- 다시 TlsGetValue로 저장된 데이터를 꺼내서 출력 후 free() 시킨다음 thread 종료
- main thread에서는 thread들이 모두 종료될 때까지 대기
- TlsFree()를 통해 할당받았던 tls_index를 해제
- 종료
여기서 중요한 체크사항 !
- TlsGetValue()로 값을 얻어오고 TlsSetValue()로 저장할 수있는 데이터는 LPVOID로 address의 크기만큼만 저장 가능하다. (이거만 지원한다고 함.)즉, 4byte 또는 8byte만 저장가능. 따라서 크기가 큰 struct를 저장하기 위해서는 동적할당 후 pointer를 저장해야한다.
- UNIX의 TLS와는 달리 destructor 기능이 없다. 따라서 위 예제의 5번과 같이 thread에서 동적할당한 data의 경우 직접 free를 진행해야한다.
이 외에 추가로 테스트하면서 얻은 정보
- TlsAlloc - TlsFree를 main thread가 아닌 working thread 내부에서 할당받아서 사용해봤는데 TlsFree시 다른 thread에서 TlsGetValue로 접근할 수 없는 문제가 발생하였다. 이런 경우에는 TlsGetValue를 하면 NULL이 return 되니 주의해야한다. (TlsAlloc - TlsFree는 working thread에서 하면 안될듯 <물론 쓸 수 있게 끔 코드를 짤수는 있겠지만, tls의 취지를 생각하면 썩 좋은 방향은 아닐꺼같다.>)
- TlsAlloc으로 주는 index는 재사용을 하고 index의 max치가 있다. TlsAlloc을 할때마다 index가 늘어나며, TlsAlloc 전에 사용중인 index를 free를 한 경우 바로 재사용이 되는걸 확인하였다.
유용한 정보
- Tls를 dll에서 사용하는 경우 DllMain을 사용하면 굉장히 편리하고 안전하게 사용가능하다. 다만, DllMain이 반드시 main thread에서 시작할꺼라는 생각을 하면 안된다.(dll에서 Tls를 사용하는 좋은 예제는 https://docs.microsoft.com/ko-kr/windows/desktop/Dlls/using-thread-local-storage-in-a-dynamic-link-library 참고)
댓글 없음:
댓글 쓰기