スレッド ローカル ストレージ (TLS: Thread Local Storage)
スレッド ローカル ストレージ (TLS) は、指定されたマルチスレッド プロセスの各スレッドが、スレッド固有のデータを格納する場所を割り当てるための手段です。 (実行時に) 動的にバインドされるスレッド固有のデータは、TLS API (TlsAlloc) によってサポートされています。 Win32 および Microsoft C++ コンパイラでは、既存の API 実装に加えて、(読み込み時に) スレッドごとに静的にバインドされるデータもサポートされるようになりました。
TLS のコンパイラ実装
C++11: オブジェクトとクラス メンバーにスレッド ローカル ストレージを指定するには、thread_local
ストレージ クラス指定子を使用することをお勧めします。 詳細については、ストレージ クラス (C++) に関する記事を参照してください。
MSVC には、拡張ストレージ クラス修飾子として、Microsoft 固有の属性である thread も用意されています。 thread
変数は、__declspec
キーワードを使用して宣言します。 たとえば、次に示すコードは、整数型のスレッド ローカル変数を宣言して特定の値に初期化します。
__declspec( thread ) int tls_i = 1;
規則と制約
静的にバインドされるスレッド ローカル オブジェクトと変数を宣言する場合は、次のガイドラインに従ってください。 これらのガイドラインは、thread と thread_local の両方に適用されます。
thread
属性は、クラスとデータの宣言と定義にのみ適用できます。 関数の宣言や定義には使用できません。 たとえば、次のコードはコンパイラ エラーになります。__declspec( thread )void func(); // This will generate an error.
thread
修飾子は、エクステントがstatic
のデータ項目でのみ指定できます。 これには、グローバル データ オブジェクト (static
とextern
の両方)、ローカル静的オブジェクト、C++ クラスの静的データ メンバーが含まれます。 自動データ オブジェクトは、thread
属性を使用して宣言することはできません。 次のコードはコンパイラ エラーになります。void func1() { __declspec( thread )int tls_i; // This will generate an error. } int func2(__declspec( thread )int tls_i ) // This will generate an error. { return tls_i; }
スレッド ローカル オブジェクトの宣言と定義では、すべて
thread
属性を指定する必要があります。 たとえば、次のコードはエラーになります。#define Thread __declspec( thread ) extern int tls_i; // This will generate an error, since the int __declspec( thread )tls_i; // declaration and definition differ.
thread
属性は、型修飾子として使用することはできません。 たとえば、次のコードはコンパイラ エラーになります。char __declspec( thread ) *ch; // Error
thread
属性を使用する C++ オブジェクトの宣言は許可されるので、次の 2 つの例は同じ意味になります。__declspec( thread ) class B { // Code } BObject; // OK--BObject is declared thread local. class B { // Code }; __declspec( thread ) B BObject; // OK--BObject is declared thread local.
スレッド ローカル オブジェクトのアドレスは定数とは見なされません。また、そのようなアドレスが含まれている式は定数式とは見なされません。 標準 C では、この影響で、オブジェクトまたはポインターの初期化子としてスレッド ローカル変数のアドレスを使用することが禁止されています。 たとえば、次のコードは、C コンパイラによってエラーとしてフラグが設定されます。
__declspec( thread ) int tls_i; int *p = &tls_i; //This will generate an error in C.
この制限は C++ では適用されません。 C++ ではすべてのオブジェクトを動的に初期化することが許可されているため、スレッド ローカル変数のアドレスを使用する式を使用してオブジェクトを初期化できます。 これは、スレッド ローカル オブジェクトの構築と同様に行われます。 たとえば、前に示したコードを C++ ソース ファイルとしてコンパイルしてもエラーは生成されません。 スレッド ローカル変数のアドレスが有効なのは、アドレスが取得されたスレッドが存在する間だけです。
標準 C では、オブジェクトや変数をそれ自体への参照を含む式で初期化できますが、非静的エクステントのオブジェクトに限られます。 C++ では、一般に、オブジェクト自体への参照を含む式でこのようにオブジェクトを動的に初期化できますが、この種の初期化はスレッド ローカル オブジェクトでは許可されません。 次に例を示します。
__declspec( thread )int tls_i = tls_i; // Error in C and C++ int j = j; // OK in C++, error in C __declspec( thread )int tls_i = sizeof( tls_i ) // Legal in C and C++
初期化されるオブジェクトが含まれる
sizeof
式は、そのオブジェクト自体への参照を表さないため、C と C++ の両方で有効になります。スレッド ローカル ストレージ機能は将来拡張される可能性があるため、C++ ではこのようにスレッド データを動的に初期化することはできません。
Windows Vista より前の Windows オペレーティング システムでは、
__declspec( thread )
にいくつかの制限があります。 DLL で任意のデータまたはオブジェクトを__declspec( thread )
として宣言すると、動的に読み込まれた場合に保護違反が発生する可能性があります。 LoadLibrary を使用して DLL が読み込まれると、コードが__declspec( thread )
データを参照するたびにシステム エラーが発生します。 スレッドのグローバル変数領域は実行時に割り当てられるため、この領域のサイズは、アプリケーションの要件および静的にリンクされているすべての DLL の要件に基づいて計算されます。LoadLibrary
を使用する場合、__declspec( thread )
を使用して宣言されたスレッド ローカル変数用にこの領域を拡張することはできません。LoadLibrary
を使用して DLL が読み込まれる可能性がある場合は、DLL で TLS API (TlsAlloc など) を使用して TLS を割り当ててください。