제목에 비해선 좀 작은 주제를 하나 :)
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 ((Plain-Old-Data. integer 호환되는 녀석들과 그걸 정적으로 묶어서 만드는 것들. Non-POD는 반대의 의미)) 타입을 선언하는 것은 불가능하다.
오늘 당한 일
기반 라이브러리에 커밋된 코드를 병합해와서 작업을 하는데 테스트가 제대로 안돈다 — CI서버에서 테스트가 timeout이 났음. 원인을 파악해보니 저 것 — General Protection Error.
추가된 코드에 의미상 확실히 __declspec(thread)를 쓰는 코드가 있어서 거기 접근하는 경로에서는 무조건 죽게 되는 것. 사실 그냥 돌릴 때는 문제가 없는데 (그래서 CI는 못잡고…), VUTPP를 써서 테스트할 때는 DLL로 만들어서 명시적으로 동적 링크(LoadLibarry) 하기 때문에, 링커/로더 기반의 작업이 제대로 안되서 __declspec(thread) 코드가 사망
O <-<
> Generatl Protection Error
은 먼가욤?
스샷 확보했으니… 고치고 배째도 소용없…
뭐긴 뭡니까 오타죠
-,-
저런 깔끔한 방법이 있군요.
내용이 짧고, 의미가 간결하긴하지만, 제약도 많은 방법이지;
TLS, DLL, 그리고 Legacy Windows…
오늘 같은 팀 선배가 당한 일 하나. 이미 2년 정도 클라이언트 쪽에서 사용할 라이브러리를 작성하는데, 이 라이브러리를 사용하는 다른 프로젝트 k개는 잘 동작하는 상황. 근데 이걸 최근에 적용한(…) 모 팀에서 이런 보고가 들어왔다 “예제로 주신 프로그램은 잘 동작하는데, 저희 프로그램에서 사용하면 바로 크래시 합니다” ?! 제목을 보고 유추했을 지도 모르지만 – 그리고 클라이언트 라이브러리 문제란 점에서 감을 잡았다면 더더욱 추측할 수 있을 듯 …