Post List

Thread Local Storage (TLS)


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;
}

실행 결과는 직접 확인하는걸로.

코드를 글로 풀이하자면
  1. 전역변수 tls_index에  TlsAlloc() 을 통해 할당받은 index를 저장
  2. thread 들을 실행
  3. TlsGetValue() 함수를 통해 값을 얻어오고 값이 없으면 동적 할당
  4. 동적 할당한 영역에 데이터를 저장하고 TlsSetValue()로 저장
  5. 다시 TlsGetValue로 저장된 데이터를 꺼내서 출력 후 free() 시킨다음 thread 종료
  6. main thread에서는 thread들이 모두 종료될 때까지 대기
  7. TlsFree()를 통해 할당받았던 tls_index를 해제
  8. 종료


여기서 중요한 체크사항 !
  • 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를 한 경우 바로 재사용이 되는걸 확인하였다.

유용한 정보


댓글 없음:

댓글 쓰기