スレッド ローカル ストレージ (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;

規則と制約

静的にバインドされるスレッド ローカル オブジェクトと変数を宣言する場合は、次のガイドラインに従ってください。 これらのガイドラインは、threadthread_local の両方に適用されます。

  • thread 属性は、クラスとデータの宣言と定義にのみ適用できます。 関数の宣言や定義には使用できません。 たとえば、次のコードはコンパイラ エラーになります。

    __declspec( thread )void func();     // This will generate an error.
    
  • thread 修飾子は、エクステントが static のデータ項目でのみ指定できます。 これには、グローバル データ オブジェクト (staticextern の両方)、ローカル静的オブジェクト、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 を割り当ててください。

関連項目

C と Win32 を使用するマルチスレッド