# <3> スレッドローカル変数 (Delphi コンカレントプログラミング) --- tags: Delphi プログラミング Pascal embarcadero objectpascal created_at: 2021-12-04 updated_at: 2024-05-22 --- # 3. スレッドローカル変数 (スレッドローカルストレージ) 各スレッドがスレッド固有の情報を保持できる記憶領域の事を**スレッドローカルストレージ (TLS)**といい、Delphi では**スレッドローカル変数**として実装されています。いわゆる **Thread-Specific Storage** です。 **See also:** - [スレッド局所記憶 (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E5%B1%80%E6%89%80%E8%A8%98%E6%86%B6) ## 3.1. threadvar 変数を宣言する時に **var** ではなく **threadvar** で宣言すると、スレッドローカル変数を宣言できます。 スレッドローカル変数には次のような制限があります。 - 手続きまたは関数の内部では宣言できない (ユニットレベルでしか使えない) - 初期化を含めることができない - **absolute** 指令を指定できない - 長い文字列、ワイド文字列、動的配列、バリアント、インターフェイスなどの構造化型はスレッドが終了する前に使用したヒープ領域を解放する必要がある :::note warn スレッドローカル変数に構造化型を指定する事は推奨されていません。 ::: ### 3.1.1. threadvar の使い方 **threadvar** はスレッドでローカルとなるため、クリティカルセクションを設けて保護する必要はありません。もちろん、スレッドローカル変数の値を別のスレッドで参照する事もできません。 次のコードはフォームにメモとボタンが一つずつある想定です。実行してみるとスレッドローカル変数がどういう動きをするのか理解できると思います。 ```pascal ... threadvar tvValue: Integer; ... procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; tvValue := 100; // メインスレッドの tvValue Memo1.Lines.Add('#1: ' + tvValue.ToString); // メインスレッドの tvValue なので 100 Inc(tvValue); // メインスレッドの tvValue + 1 (101) // Thread A TThread.CreateAnonymousThread( procedure begin Inc(tvValue); // Thread A の tvValue + 1 (1) var dmyValue := tvValue; TThread.Synchronize(TThread.CurrentThread, procedure begin Memo1.Lines.Add('#2: ' + tvValue.ToString); // メインスレッドの tvValue なので 101 Memo1.Lines.Add('#3: ' + dmyValue.ToString); // Thread A の dmyValue の値なので 1 end); end ).Start; // Thread B TThread.CreateAnonymousThread( procedure begin Inc(tvValue, 2); // Thread B の tvValue + 2 (2) var dmyValue := tvValue; TThread.Synchronize(TThread.CurrentThread, procedure begin Memo1.Lines.Add('#4: ' + tvValue.ToString); // メインスレッドの tvValue なので 101 Memo1.Lines.Add('#5: ' + dmyValue.ToString); // Thread B の dmyValue の値なので 2 end); end ).Start; Memo1.Lines.Add('#6: ' + tvValue.ToString); // メインスレッドの tvValue なので 101 end; ``` 通常は **threadvar** でスレッドローカル変数を宣言せずに、スレッドクラスのフィールド (変数) を使うべきかと思います。 - `BeginThread()` ルーチンを使った時 - スレッドクラスにフィールドを追加したくない時 (あるの?) - コードブロックがどのスレッドで実行されているか判らない時のためのデバッグ用変数として あえて **threadvar** を使わなくてはならないケースがあるとしたらこれくらいでしょうか? :::note warn DLL 内で threadvar を使うと問題が発生する事があります。 心当たりがある方は、後述するスレッドローカルストレージ API の利用を検討してください。 ::: ### 3.1.2. スレッドローカル変数が利用したヒープ領域の解放 先述の通り、スレッドローカル変数に構造化型を使った場合には自前で領域を解放する必要があります。次のようなコードはメモリリークを起こします。 ```pascal threadvar sName : string; vData: variant; dArr: array of Integer; ... sName := 'John Doe'; vData := 'Jane Doe'; dArr := [100, 200]; ``` `Finalize()` 手続きを使うとスレッドローカル変数が利用したヒープ領域を解放できます。 ```pascal Finalize(sName); Finalize(vData); Finalize(dArr); ``` **See also:** - [System.Finalize (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Finalize) - [【Delphi】String 型変数で身に覚えのないメモリリークが報告される (Qiita)](./c3912518bb9c4e18b40a.md) ### 3.1.3. class threadvar ドキュメントには記載がありませんが、クラスフィールドとしての **threadvar** も利用可能です [^1]。 ```pascal TMyThread = class(TThread) private { Private 宣言 } protected procedure Execute; override; public class threadvar RecordCount: Integer; end; ``` このように定義されたスレッドローカル変数 `RecordCount` は **public** なので、インスタンス化していなくても `TMyThread.RecordCount` としてアクセスできます。 **See also:** - [クラスフィールド (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%EF%BC%88Delphi%EF%BC%89) ## 3.2. スレッドローカルストレージ API Windows には (動的) スレッドローカルストレージ (Local Thread Storage) を扱うための API があります。これらの API の定義は `Winapi.Windows` にあります。 | Windows API | 説明 | |:---|:---| | [TlsAlloc()](https://docs.microsoft.com/ja-jp/windows/desktop/api/processthreadsapi/nf-processthreadsapi-tlsalloc) | スレッドローカルストレージインデックスを割り当てる | | [TlsFree()](https://docs.microsoft.com/ja-jp/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsfree) | スレッドローカルストレージインデックスを解放する | | [TlsGetValue()](https://docs.microsoft.com/ja-jp/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsgetvalue) | 指定のスレッドローカルストレージインデックスに設定されているメモリオブジェクトを返す | | [TlsSetValue()](https://docs.microsoft.com/ja-jp/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlssetvalue) | 指定のスレッドローカルストレージインデックスにメモリオブジェクトを設定する | 各プロセスで利用可能な TLS スロットの**最小数**は `TLS_MINIMUM_AVAILABLE` により定義されており、現在の Windows では `64` 個となっています (最大で 1,088 個)。Windows 2000 よりも前の Windows の場合、TLS スロットは**最大で** `64` 個でした。 :::note info Delphi では threadvar でスレッドローカル変数をいくつ宣言しても、たった一つの TLS スロットしか消費しない構造になっています。Delphi では独自で管理されるスレッドローカル変数用メモリブロックへのポインタとしてのみ TLS スロットを利用しています。 ::: **See also:** - [スレッドローカルストレージ (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/windows/win32/procthread/thread-local-storage) - [スレッド ローカル ストレージ (TLS: Thread Local Storage) (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/cpp/parallel/thread-local-storage-tls) - [ダイナミックリンクライブラリでのスレッドローカルストレージの使用 (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/windows/win32/dlls/using-thread-local-storage-in-a-dynamic-link-library) ## 3.3. TLS 用グローバル変数 `SysInit` 内には TLS 用のグローバル変数が 2 つあります。詳細なドキュメントがないのですが、恐らく次のような意味合いだと思われます。 | 変数 | 意味 | |:---|:---| | TlsIndex | Delphi が使うスレッドローカルストレージ (TLS) のインデックス。| | TlsLast | この変数のアドレスは Delphi が使う TLS 用メモリブロックの最後を指します。TLS が未使用の場合には **nil** となります。 | 検証用のコードを次に示します。 ```pascal program Project1; {$APPTYPE CONSOLE} uses System.SysUtils, System.Classes, WinAPI.Windows; threadvar a: Integer; b: Integer; c: Integer; begin var Idx := TlsAlloc; Writeln('AllocIndex:', Idx); TlsSetValue(Idx, Pointer(100)); Writeln('AllocValue:', NativeInt(TlsGetValue(Idx))); TlsFree(Idx); TThread.CreateAnonymousThread(procedure begin a := 100; b := 200; // 変数を増やしたり減らしたりしてみる end); Writeln('TlsIndex:', SysInit.TlsIndex); Writeln('TlsLast:', NativeInt(Addr(SysInit.TlsLast)).ToHexString); Readln; end. ``` 上記コードでは TLS をストレージとして利用していますが、本来は Delphi がやっているように独自に管理されたメモリブロックへのポインタとして使うべきです。 **See also:** - [SysInit.TlsIndex (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/SysInit.TlsIndex) - [SysInit.TlsLast (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/SysInit.TlsLast) # 参考 - [スレッドローカル変数 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E5%A4%89%E6%95%B0%EF%BC%88Delphi%EF%BC%89#.E3.82.B9.E3.83.AC.E3.83.83.E3.83.89.E3.83.AD.E3.83.BC.E3.82.AB.E3.83.AB.E5.A4.89.E6.95.B0) - [Creating UDFs in Delphi (EDN)](https://edn.embarcadero.com/article/25624) - [Thread Local Storage, part 1: Overview (Nynaeve)](http://www.nynaeve.net/?p=180) - [Thread Local Storage, part 2: Explicit TLS (Nynaeve)](http://www.nynaeve.net/?p=181) - [Thread Local Storage, part 3: Compiler and linker support for implicit TLS (Nynaeve)](http://www.nynaeve.net/?p=183) - [Thread Local Storage, part 4: Accessing __declspec(thread) data (Nynaeve)](http://www.nynaeve.net/?p=185) - [Thread Local Storage, part 5: Loader support for __declspec(thread) variables (process initialization time) (Nynaeve)](http://www.nynaeve.net/?p=186) - [Thread Local Storage, part 6: Design problems with the Windows Server 2003 (and earlier) approach to implicit TLS (Nynaeve)](http://www.nynaeve.net/?p=187) - [Thread Local Storage, part 7: Windows Vista support for __declspec(thread) in demand loaded DLLs (Nynaeve)](http://www.nynaeve.net/?p=189) - [Thread Local Storage, part 8: Wrap-up (Nynaeve)](http://www.nynaeve.net/?p=190) # 索引 [ [← 2. クリティカルセクションとロック](./9f3c7534e2ac406ba7dc.md) ] [ [↑ 目次へ](./e8c1ff3a4c74e4c2a4f3.md) ] [ [→ 4. 並列プログラミングライブラリ (PPL)](./ee155618119c48bec7b7.md) ] [^1]: クラスフィールド **class threadvar** は Delphi XE3 以降で利用可能です。