TLS
スレッドごとの静的変数
全てのスレッドは、グローバル変数と静的変数を共有します
これは、スレッドがプロセスと異なり、プロセスのメモリを共有しているからです
スレッドが持つ独自の変数は、それぞれのスタックに積まれる動的な変数だけです
では、スレッド単位の静的変数が必要な時はどうするのでしょうか?
つまり、そのスレッドはどの位置からでもアクセスできるグローバルな記憶領域であり
それでいて、他のスレッドはアクセスすることができないという記憶領域です
このような機能は、CPU 等のハードウェアには搭載されていません
そのため、記憶領域とスレッドの管理をソフトウェアで行う必要があるのです
実現の方法としては、スレッド ID と変数のアドレスで一つのデータテーブルを作成し
スレッド ID と変数のアドレスを関連付けて、管理する方法が考えられます
スレッドが変数を呼び出すと、テーブルからスレッド ID を検索し
その ID に関連付けられている変数へのポインタを返すようにすれば
スレッド単位でユニークな静的変数を実現することができるでしょう
この機能を、あなたのアプリケーションで独自に取り付けることもできると思いますが
Windows API は スレッドローカルストレージ(TLS) をサポートしています
これを使えば、簡単にスレッド単位の静的変数を実現することができるでしょう
スレッドローカルストレージを利用するには TlsAlloc() を最初に使います
これは、スレッドローカルストレージのインデックスを割り当てます
DWORD TlsAlloc(VOID)
この関数は、スレッドローカルストレージのインデックスを返します
失敗すれば 0xFFFFFFFF が返されます
この関数で割り当てた、スレッドローカルストレージ管理用のメモリ領域は
スレッドローカルストレージが不必要になれば TlsFree() で解放します
ただし、関連付けたデータそのものが解放されることはありません
BOOL TlsFree(DWORD dwTlsIndex);
dwTlsIndex には、解放したい TLS インデックスを指定します
成功すれば 0 以外、失敗すれば 0 が返ります
スレッドローカルストレージに、メモリを関連付けるには
TlsSetValue() 関数で、スレッドローカルストレージスロットにポインタをストアします
BOOL TlsSetValue(DWORD dwTlsIndex , LPVOID lpTlsValue);
dwTlsIndex には、TLS インデックスを指定します
lpTlsValue は、ストアするメモリへのポインタを指定します
成功した時は 0 以外、失敗した時は 0 が返ります
ここで指定したポインタのことを、スレッド相対データポインタと呼びます
ストアしたスレッド相対データポインタを取得するには TlsGetValue() を使います
LPVOID TlsGetValue(DWORD dwTlsIndex);
dwTlsIndex には、TLS インデックスを指定します
成功すれば、割り当てられているメモリへのポインタが、失敗すれば 0 が返ります
呼び出したスレッドに関連した、メモリ要素がこれで取得できるようになります
#include <windows.h>
DWORD dwTLSIndex = 0;
VOID DrawThreadText(HWND hWnd) {
static int iY = 0;
TEXTMETRIC tm;
HDC hdc;
LPVOID pText = TlsGetValue(dwTLSIndex);
hdc = GetDC(hWnd);
TextOut(hdc , 0 , iY , pText , lstrlen(pText));
GetTextMetrics(hdc , &tm);
iY = iY > 600 ? 0 : iY + tm.tmHeight;
ReleaseDC(hWnd , hdc);
}
DWORD WINAPI ThreadFunc1(LPVOID hWnd) {
TlsSetValue(dwTLSIndex , TEXT("Kitty on your lap"));
DrawThreadText(hWnd);
ExitThread(TRUE);
}
DWORD WINAPI ThreadFunc2(LPVOID hWnd) {
TlsSetValue(dwTLSIndex , TEXT("Tokyo mew mew"));
DrawThreadText(hWnd);
ExitThread(TRUE);
}
LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
DWORD dwID;
switch (msg) {
case WM_DESTROY:
TlsFree(dwTLSIndex);
PostQuitMessage(0);
return 0;
case WM_CREATE:
dwTLSIndex = TlsAlloc();
return 0;
case WM_LBUTTONUP:
CreateThread(NULL , 0 , ThreadFunc1 , hWnd , 0 , &dwID);
CreateThread(NULL , 0 , ThreadFunc2 , hWnd , 0 , &dwID);
return 0;
}
return DefWindowProc(hWnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow) {
HWND hWnd;
MSG msg;
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return 1;
hWnd = CreateWindow(
TEXT("KITTY") , TEXT("Kitty on your lap") ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hWnd == NULL) return 1;
while (GetMessage(&msg , NULL , 0 , 0 )) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
このプログラムを実行し、ウィンドウのクライアント領域を左クリックすると
スレッドが二つ作成され、それぞれ、独自のエントリポイントから実行されます
二つのスレッドは、DrawThreadText() 関数を呼び出します
この関数は、スレッドに割り当てられたメモリ要素を取得します
プログラムの仕様上、スレッド相対データポインタは文字列へのポインタとします
そして、DrawThreadText() 関数は、この文字列へのポインタから画面に文字を描画します
DrawThreadText() 関数の iY 静的変数は、全てのスレッドに共有されます
しかし、スレッドローカルストレージを使えば
スレッド固有の変数にグローバルアクセスし、それを読み書きすることができるのです
TlsAlloc()
DWORD TlsAlloc(VOID)
スレッドローカルストレージのインデックスを割り当てます
戻り値 - TLSのインデックス、失敗すれば 0xFFFFFFFF
TlsFree()
BOOL TlsFree(DWORD dwTlsIndex);
スレッドローカルストレージのインデックスを解放します
dwTlsIndex - 解放したい TLS インデックスを指定します
戻り値 - 成功すれば 0 以外、失敗すれば 0
TlsSetValue()
BOOL TlsSetValue(DWORD dwTlsIndex , LPVOID lpTlsValue);
TLS インデックスに、メモリポインタを設定します
dwTlsIndex - TLS インデックスを指定します
lpTlsValue - ストアするメモリへのポインタを指定します
戻り値 - 成功した時は 0 以外、失敗した時は 0
TlsGetValue()
LPVOID TlsGetValue(DWORD dwTlsIndex);
TLS に設定されているメモリへのポインタを返します
dwTlsIndex - TLS インデックスを指定します
戻り値 - 割り当てられているメモリへのポインタ、失敗すれば 0