Thread Local Storage on Win32

제목에 비해선 좀 작은 주제를 하나 :)

Win32 API — 좀 더 구체적으로는 MSVC / intel CC의 일부 — 에서는 thread-local 하다는 것을 C/C++ 키워드로 표현할 수 있다.

static inline int getMid(BYTE* array, const int size, 
    const int index)
{    // find median value in array
    int i;
    __declspec(thread) static int count[256];    // buckets
    memset(count, 0, sizeof(int) * 256);
    for(i = 0; i < size; ++i)
        ++count[array[i]];
    int cul = 0;
    for(i = 0; (cul <= index) && (i < 256); ++i)
        cul += count[i];
    return i - 1;                               
}

여기에서 __declspec(thread)로 선언된 정적 배열이 TLS로 선언된 것이다.

사실 thread-local-storage라면 per-thread로 할당만 되면되지만, Windows Linker&loader는 저걸 자동으로 thread마다 있는 것처럼 사용할 수 있게 해준다. Thread local storage가 사용될 때의 최대 장점은, 스레드 수를 미리 알지 못하고도, 혹은 의미상 스레드 마다 하나씩 있으면 편한 것을 할 수 있다는 것인데 — 물론 성능문제와도 결부된다. Link/load 타임에 생성되는 주소 쪽이 동적으로 계산한 주소보다 훨씬 효율적일 수 있는 것은 당연지사 — 덕분에 고민할 거리가 좀 줄어든다.

위의 코드는 그제 포스팅했던 2D 이미지 병렬 처리 예제 만든다고 썼던 코든데, 원래의 싱글 스레드 버젼에서의 static 배열을 키워드 하나만 붙여서 멀티스레드에서도 reentrant하게 쓸 수 있게 되었다.

제약사항

하지만 이 키워드에도 명확한 제약이 있다. 컴파일러가 전부 생성해내는게 아니라 링커/로더와 연관된 문제라서, DLL 사용시에는 매우 주의해야한다. 특히 implicit 하게 컴파일 타임에 링크가 결정되는게 아니면 — 예를 들어서 LoadLibrary 함수로 동적 로딩할 때 — __declspec(thread) 로 선언한 장소에 읽거나 쓰는 순간,

General Protection Error

를 보게 된다. 즉, 링커와 로더가 어찌 처리할 수 없기 때문에, 해당 공간 자체가 생기지 않는다. 그리고 저 선언 특성상 컴파일러가 코드를 못만드는 부분도 있어서 C++ 문법 모두가 저 구문에 쓸 수 있는게 아니다. 선언과 동시에 선언을 써서 뭔가 한다거나, Non-POD[1] 타입을 선언하는 것은 불가능하다.

오늘 당한 일

기반 라이브러리에 커밋된 코드를 병합해와서 작업을 하는데 테스트가 제대로 안돈다 — CI서버에서 테스트가 timeout이 났음. 원인을 파악해보니 저 것 — General Protection Error.

추가된 코드에 의미상 확실히 __declspec(thread)를 쓰는 코드가 있어서 거기 접근하는 경로에서는 무조건 죽게 되는 것. 사실 그냥 돌릴 때는 문제가 없는데 (그래서 CI는 못잡고…), VUTPP를 써서 테스트할 때는 DLL로 만들어서 명시적으로 동적 링크(LoadLibarry) 하기 때문에, 링커/로더 기반의 작업이 제대로 안되서 __declspec(thread) 코드가 사망

O <-<

  1. Plain-Old-Data. integer 호환되는 녀석들과 그걸 정적으로 묶어서 만드는 것들. Non-POD는 반대의 의미 []

Author: rein

나는 ...

5 thoughts on “Thread Local Storage on Win32”

  1. Pingback: rein's world

Leave a Reply