3. スレッドローカル変数 (スレッドローカルストレージ)
各スレッドがスレッド固有の情報を保持できる記憶領域の事をスレッドローカルストレージ (TLS)といい、Delphi ではスレッドローカル変数として実装されています。いわゆる Thread-Specific Storage です。
See also:
3.1. threadvar
変数を宣言する時に var ではなく threadvar で宣言すると、スレッドローカル変数を宣言できます。
スレッドローカル変数には次のような制限があります。
- 手続きまたは関数の内部では宣言できない (ユニットレベルでしか使えない)
- 初期化を含めることができない
- absolute 指令を指定できない
- 長い文字列、ワイド文字列、動的配列、バリアント、インターフェイスなどの構造化型はスレッドが終了する前に使用したヒープ領域を解放する必要がある
スレッドローカル変数に構造化型を指定する事は推奨されていません。
3.1.1. threadvar の使い方
threadvar はスレッドでローカルとなるため、クリティカルセクションを設けて保護する必要はありません。もちろん、スレッドローカル変数の値を別のスレッドで参照する事もできません。
次のコードはフォームにメモとボタンが一つずつある想定です。実行してみるとスレッドローカル変数がどういう動きをするのか理解できると思います。
...
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 を使わなくてはならないケースがあるとしたらこれくらいでしょうか?
DLL 内で threadvar を使うと問題が発生する事があります。
心当たりがある方は、後述するスレッドローカルストレージ API の利用を検討してください。
3.1.2. スレッドローカル変数が利用したヒープ領域の解放
先述の通り、スレッドローカル変数に構造化型を使った場合には自前で領域を解放する必要があります。次のようなコードはメモリリークを起こします。
threadvar
sName : string;
vData: variant;
dArr: array of Integer;
...
sName := 'John Doe';
vData := 'Jane Doe';
dArr := [100, 200];
Finalize()
手続きを使うとスレッドローカル変数が利用したヒープ領域を解放できます。
Finalize(sName);
Finalize(vData);
Finalize(dArr);
See also:
3.1.3. class threadvar
ドキュメントには記載がありませんが、クラスフィールドとしての threadvar も利用可能です 1。
TMyThread = class(TThread)
private
{ Private 宣言 }
protected
procedure Execute; override;
public
class threadvar
RecordCount: Integer;
end;
このように定義されたスレッドローカル変数 RecordCount
は public なので、インスタンス化していなくても TMyThread.RecordCount
としてアクセスできます。
See also:
3.2. スレッドローカルストレージ API
Windows には (動的) スレッドローカルストレージ (Local Thread Storage) を扱うための API があります。これらの API の定義は Winapi.Windows
にあります。
Windows API | 説明 |
---|---|
TlsAlloc() | スレッドローカルストレージインデックスを割り当てる |
TlsFree() | スレッドローカルストレージインデックスを解放する |
TlsGetValue() | 指定のスレッドローカルストレージインデックスに設定されているメモリオブジェクトを返す |
TlsSetValue() | 指定のスレッドローカルストレージインデックスにメモリオブジェクトを設定する |
各プロセスで利用可能な TLS スロットの最小数は TLS_MINIMUM_AVAILABLE
により定義されており、現在の Windows では 64
個となっています (最大で 1,088 個)。Windows 2000 よりも前の Windows の場合、TLS スロットは最大で 64
個でした。
Delphi では threadvar でスレッドローカル変数をいくつ宣言しても、たった一つの TLS スロットしか消費しない構造になっています。Delphi では独自で管理されるスレッドローカル変数用メモリブロックへのポインタとしてのみ TLS スロットを利用しています。
See also:
- スレッドローカルストレージ (docs.microsoft.com)
- スレッド ローカル ストレージ (TLS: Thread Local Storage) (docs.microsoft.com)
- ダイナミックリンクライブラリでのスレッドローカルストレージの使用 (docs.microsoft.com)
3.3. TLS 用グローバル変数
SysInit
内には TLS 用のグローバル変数が 2 つあります。詳細なドキュメントがないのですが、恐らく次のような意味合いだと思われます。
変数 | 意味 |
---|---|
TlsIndex | Delphi が使うスレッドローカルストレージ (TLS) のインデックス。 |
TlsLast | この変数のアドレスは Delphi が使う TLS 用メモリブロックの最後を指します。TLS が未使用の場合には nil となります。 |
検証用のコードを次に示します。
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:
参考
- スレッドローカル変数 (DocWiki)
- Creating UDFs in Delphi (EDN)
- Thread Local Storage, part 1: Overview (Nynaeve)
- Thread Local Storage, part 2: Explicit TLS (Nynaeve)
- Thread Local Storage, part 3: Compiler and linker support for implicit TLS (Nynaeve)
- Thread Local Storage, part 4: Accessing __declspec(thread) data (Nynaeve)
- Thread Local Storage, part 5: Loader support for __declspec(thread) variables (process initialization time) (Nynaeve)
- Thread Local Storage, part 6: Design problems with the Windows Server 2003 (and earlier) approach to implicit TLS (Nynaeve)
- Thread Local Storage, part 7: Windows Vista support for __declspec(thread) in demand loaded DLLs (Nynaeve)
- Thread Local Storage, part 8: Wrap-up (Nynaeve)
索引
[ ← 2. クリティカルセクションとロック ] [ ↑ 目次へ ] [ → 4. 並列プログラミングライブラリ (PPL) ]
-
クラスフィールド class threadvar は Delphi XE3 以降で利用可能です。 ↩