スレッド・ローカル・ストレージ

マルチスレッドプログラムでは、共有データに対する複数のスレッドからのアクセスには注意を払う必要があります。
(→スレッド間のクリティカルセクションを参照)
グローバル変数は全てのスレッド(というよりプログラム全体)に共通のデータですが、スレッド・ローカル・ストレージを使用すればスレッド毎に独立したデータを作ることができます。
同一スレッドからは共有できる、「スレッドに固有なグローバル変数」に近いものです。
(スレッドに対して局所的(ローカル)な記憶領域(ストレージ)という意味)

「スレッド・ローカル・ストレージ」ではちょっと長いので以降はTLSと略記します。

スレッド・ローカル・ストレージの作成

TlsAlloc関数

TLSはTlsAlloc関数で作成します。

DWORD TlsAlloc();
TLSインデックスを割り当てる。
失敗した場合はTLS_OUT_OF_INDEXESを返す。

引数はなく、戻り値をDWORD型変数に取得するだけです。

TLSはプロセス(起動中の実行ファイル)に共通のインデックス(番号)を使用して、スレッド毎に独立したデータを作成します。
この値が実際に何であるかはプログラマは知る必要はなく、後述するTLS関数にそのまま渡して使用します。

関数が失敗した場合はTLS_OUT_OF_INDEXESという定数を返します。

TLSインデックスは少なくとも64個まで作成可能です。

TlsSetValue関数

スレッド固有の値はTlsSetValue関数で書き込みます。

BOOL TlsSetValue(
 DWORD dwTlsIndex,
 LPVOID lpTlsValue
);
TLSインデックスdwTlsIndexのTLSスロットに、スレッド固有の任意の値lpTlsValueを割り当てる。
成功した場合は0以外を、失敗した場合は0を返す。

第一引数はTlsAlloc関数の戻り値であるTLSインデックスを指定します。
第二引数は割り当てたいスレッド固有の値ですが、LPVOID型(void*型)なのでポインタのほか、int型までのサイズの整数値を割り当てることができます。

この関数はスレッド固有データを割り当てたいスレッドから呼び出します。
TLSインデックスは共通ですが、値は「TLSスロット」というスレッド毎に独立した場所に保存されます。
同じスレッドからの呼び出しであれば同じスレッドに対してアクセスされ、別のスレッドからの呼び出しならばまた別のスロットにアクセスされます。

TlsGetValue関数

保存したスレッド固有の値はTlsGetValue関数で読み取ります。

LPVOID TlsGetValue(
 DWORD dwTlsIndex
);
TLSインデックスdwTlsIndexのTLSスロットから、スレッド固有の値を取得する。

第一引数はTlsAlloc関数の戻り値であるTLSインデックスを指定します。
TLSスロットに何も値を書き込んでいない場合は0が取得されます。

TlsFree関数

不要になったTLSインデックスはTlsFree関数で解放します。

BOOL TlsFree(
 DWORD dwTlsIndex
);
TLSインデックスdwTlsIndexを解放する。
成功した場合は0以外を、失敗した場合は0を返す。

この関数は指定のTLSインデックスのTLSスロットの値を消去しますが、ポインタを格納していた場合、ポインタの先のデータは消去しません。
自分で確保したメモリ領域などはこの関数の呼び出し前に解放しておく必要があります。

サンプルコード


#include <windows.h>
#include <process.h>
#include <strsafe.h>

//ウィンドウの生成等は省略

//TLSインデックス
DWORD dwTlsIndex;

//TLSスロットから値(文字列)を読み取り表示する
void ShowTlsValue(HWND hWnd)
{
	//TlsGetValue関数を呼び出したスレッドによって
	//取得される値が変わる
	WCHAR *str = (WCHAR*)TlsGetValue(dwTlsIndex);
	MessageBox(hWnd,
		str ? str : L"",
		L"情報",
		MB_OK);
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
	TlsSetValue(dwTlsIndex, (LPVOID)L"Thread1");
	ShowTlsValue((HWND)lpParameter);
	return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
	WCHAR* txt = (WCHAR*)malloc(sizeof(WCHAR) * BUFFERSIZE);
	StringCchPrintf(txt, BUFFERSIZE, L"Thread2");
	TlsSetValue(dwTlsIndex, (LPVOID)txt);
	ShowTlsValue((HWND)lpParameter);

	//スレッドの終了前に必ずメモリを解放する
	free(txt);
	return 0;
}

#define IDC_BUTTON1 100
#define IDC_BUTTON2 101

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HANDLE hThread;

	switch (message)
	{
	case WM_CREATE: //ウィンドウの作成
		//TLSインデックスの割り当て
		dwTlsIndex = TlsAlloc();

		CreateWindow(L"BUTTON", L"スレッド1",
			WS_CHILD | WS_VISIBLE,
			10, 10,
			120, 25,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		CreateWindow(L"BUTTON", L"スレッド2",
			WS_CHILD | WS_VISIBLE,
			140, 10,
			120, 25,
			hWnd, (HMENU)IDC_BUTTON2, hInst, NULL);
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			hThread = (HANDLE)_beginthreadex(
				NULL, 0,
				(LPTHREAD_START_ROUTINE)ThreadProc1,
				hWnd, 0, NULL);
			if (hThread != NULL)
				CloseHandle(hThread);
			break;

		case IDC_BUTTON2:
			hThread = (HANDLE)_beginthreadex(
				NULL, 0,
				(LPTHREAD_START_ROUTINE)ThreadProc2,
				hWnd, 0, NULL);
			if (hThread != NULL)
				CloseHandle(hThread);
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		//TLSの解放
		TlsFree(dwTlsIndex);
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

新規作成されるスレッドの処理はTLSに保存する文字列が異なります。
自作関数ShowTlsValueはTlsGetValue関数によりTLSスロットの値を読み取って表示しているだけですが、この関数を呼び出したスレッド毎に異なる値(文字列ポインタ)を取得します。
スレッド・ローカル・ストレージのサンプルコード

TLSスロットは、スレッドの開始時に保存のためのメモリ領域が確保され、スレッドの終了時に破棄されます。
上記のコードはボタンをクリックする度に新しいスレッドを作成してすぐに破棄しているので、その都度メモリの確保と破棄が行われています。

スレッド開始関数ThreadProc2では、スレッド内で動的確保したメモリ(malloc関数)のポインタを保存しています。
このメモリはスレッドが終了する前に解放しないと他からアクセスする手段が無くなる(メモリリークの原因になる)ので注意が必要です。
再度スレッド開始関数ThreadProc2からスレッドを作成しても、以前に作成したスレッドとは無関係な別のスレッドとなるため、TLS関数で読み書きされるデータも独立しています。