# <2> クリティカルセクションとロック (Delphi コンカレントプログラミング) --- tags: Delphi プログラミング Pascal embarcadero objectpascal created_at: 2021-12-04 updated_at: 2024-12-25 --- # 2. クリティカルセクションとロック **クリティカルセクション**とは、同時実行されると危険な領域の事を指します。クリティカルセクションを一つのスレッドだけが実行できるようにするのが、いわゆる **Single Threaded Execution** です。この Single Threaded Execution の事を **Critical Section** と呼ぶ事もあります。混乱を避けるため、当記事では次のような意味で使っています。 | 単語 | 意味 | |:---|:---| | クリティカルセクション | 同時実行されると危険な領域 | | Critical Section | Single Threaded Execution な機構の事 | `TCriticalSection` を使うと、マルチスレッドアプリケーションにおいて、あるスレッドが、コードのブロックへ他のスレッドがアクセスするのを一時的に防ぐことができます。 `TCriticalSection` は `System.SyncObjs` で定義されています [^1]。 - [System.SyncObjs.TCriticalSection (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSection) 最初期のこのクラスは Windows API をラップしたものでした。 | Delphi | Windows API | |:---|:---| | [TCriticalSection.Create()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSection.Create) コンストラクタ | [InitializeCriticalSection()](https://docs.microsoft.com/ja-jp/windows/win32/api/synchapi/nf-synchapi-initializecriticalsection) | | [TCriticalSection.Destroy](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSection.Destroy) デストラクタ| [DeleteCriticalSection()](https://docs.microsoft.com/ja-jp/windows/win32/api/synchapi/nf-synchapi-deletecriticalsection) | | [TCriticalSection.Acquire()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSection.Acquire) メソッド /
[TCriticalSection.Enter()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSection.Enter) メソッド | [EnterCriticalSection()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-entercriticalsection) | | [TCriticalSection.Release()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSection.Suspend) メソッド /
[TCriticalSection.Leave()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSection.Enter) メソッド | [LeaveCriticalSection()](https://docs.microsoft.com/ja-jp/windows/win32/api/synchapi/nf-synchapi-leavecriticalsection) | Windows API の Critical Section は次のような使い方になります。 ```pascal ... uses ..., WinApi.Windows; ... var CriticalSection: TRTLCriticalSection; ... initialization InitializeCriticalSection(CriticalSection); finalization DeleteCriticalSection(CriticalSection); end. ``` 最近の Delphi の `TCriticalSection` クラスはマルチプラットフォーム対応なので、Windows API を直接扱う事はあまりないかと思います。 ## 2.1. TCriticalSection の使い方 `TCriticalSection` はインスタンス化して使いますが、各スレッドクラスの中でインスタンス化するコードを記述してはいけません。 ```pascal ... uses ..., System.SyncObjs; ... var CriticalSection: TCriticalSection; ... initialization CriticalSection := TCriticalSection.Create; finalization CriticalSection.Free; end. ``` 必ずグローバルな Critical Section 変数を定義する必要があります。 **See also:** - [クリティカルセクション (TCriticalSection) を使用する (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AB%E3%83%AB_%E3%82%BB%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B) ### 2.1.1. Critical Section 変数の使い方 Critical Section 変数を使ってみる前に、まずは **「使わなかったらどうなるのか?」** をテストしてみましょう。 グローバル変数 `Counter` の値を読み取って +1 し、`Counter` に書き戻すという処理をスレッド内で行います。そして、このスレッドを 100 回実行してみます。 #### ・Critical Section 変数を使わない場合 VCL アプリケーションを新規作成し、フォームにボタンを一つ追加し、 ![image.png](./images/b3106917-a75c-24f3-f65c-846400216565.png) 次のようなコードを書きます。 ```pascal:unit1.pas unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TMyThread = class(TThread) private { Private 宣言 } protected procedure Execute; override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private procedure MyThreadTerminate(Sender: TObject); { Private 宣言 } public { Public 宣言 } end; const NUM_OF_THREAD = 100; var Form1: TForm1; Counter: Integer; TermCount: Integer; implementation {$R *.dfm} procedure TForm1.MyThreadTerminate(Sender: TObject); begin Inc(TermCount); // 終了したスレッドをカウント if TermCount = NUM_OF_THREAD then // すべてのスレッドが終了したら begin ShowMessage(Counter.ToString); // Counter の値をダイアログで表示 Button1.Enabled := True; end; end; procedure TForm1.Button1Click(Sender: TObject); begin Button1.Enabled := False; Counter := 0; TermCount := 0; for var i := 1 to NUM_OF_THREAD do with TMyThread.Create(True) do begin FreeOnTerminate := True; OnTerminate := MyThreadTerminate; Start; end; end; { TMyThread } procedure TMyThread.Execute; begin var v := Counter; Inc(v); TThread.Sleep(20); Counter := v; end; end. ``` ```Unit1.dfm object Form1: TForm1 Left = 0 Top = 0 Caption = 'Form1' ClientHeight = 441 ClientWidth = 624 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -12 Font.Name = 'Segoe UI' Font.Style = [] PixelsPerInch = 96 TextHeight = 15 object Button1: TButton Left = 24 Top = 24 Width = 80 Height = 25 Caption = 'Button1' TabOrder = 0 OnClick = Button1Click end end ``` これをコンパイルして実行し、ボタンを押してみます。 ![image.png](./images/3ca0c420-9d6d-f70a-1afa-41238050a7cd.png) 表示される値はバラバラだと思いますが、すくなくとも 100 にはならないと思います。 #### ・Critical Section 変数を使った場合 次に、Critical Section 変数を使うように書き換えてみます。コードが一部省略されている事に注意してください。 ```pascal:unit1.pas unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.SyncObjs {追加}; type TMyThread = class(TThread) private { Private 宣言 } protected procedure Execute; override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private procedure MyThreadTerminate(Sender: TObject); { Private 宣言 } public { Public 宣言 } end; const NUM_OF_THREAD = 100; var Form1: TForm1; Counter: Integer; TermCount: Integer; CriticalSection: TCriticalSection; // 追加 implementation {$R *.dfm} procedure TForm1.MyThreadTerminate(Sender: TObject); [...] procedure TForm1.Button1Click(Sender: TObject); [...] { TMyThread } procedure TMyThread.Execute; begin CriticalSection.Acquire; // または CriticalSection.Enter try var v := Counter; Inc(v); TThread.Sleep(20); Counter := v; finally CriticalSection.Release; // または CriticalSection.Leave end; end; initialization CriticalSection := TCriticalSection.Create; finalization CriticalSection.Free; end. ``` そして実行してみます。 ![image.png](./images/3e3131a7-f3ee-d8f2-3bf2-1139cf848f24.png) `Sleep()` のパラメータを大きくしても小さくしても常に `100` が返ってきます。 ## 2.2. TRTLCriticalSectionHelper プラットフォーム依存ですが、`System.SyncObjs` には `TRTLCriticalSectionHelper` というレコードヘルパーが存在し [^2] `TRTLCriticalSection` レコードを `TCriticalSection` クラスのように使う事ができます。 ```pascal ... uses ..., System.SyncObjs; ... var CriticalSection: TRTLCriticalSection; ... initialization CriticalSection.Initialize; finalization CriticalSection.Destroy; // または CriticalSection.Free end. ``` `Enter()` メソッドや `Leave()` メソッドも用意されています。 ```pascal procedure TMyThread.Execute; begin CriticalSection.Enter; try var v := Counter; Inc(v); TThread.Sleep(20); Counter := v; finally CriticalSection.Free; end; end; ``` **See also:** - [System.SyncObjs.TCriticalSectionHelper (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TCriticalSectionHelper) - [Critical Section Objects (クリティカル セクション オブジェクト (docs.microsoft.com))](https://docs.microsoft.com/ja-jp/windows/win32/sync/critical-section-objects) ## 2.3. System.TMonitor **モニタ**とは - データを格納する構造体 - 同期機構 - それらを操作するメソッド を一つにまとめたもので、オブジェクト指向プログラミング (OOP) をサポートする言語だと、クラスにマルチスレッド (やマルチプロセス) の機能を持たせたものがそれに該当します。 `TMonitor` レコード [^3] は `TCriticalSection` と同じような使い方ができますが、保護する対象がオブジェクト (TObject の派生インスタンス) となります。保護する対象のオブジェクトは、単純な (レコードのような) クラスで構いません。 ```pascal type TCounter = class FCounter: Integer; end; ... var Counter: TCounter; ... initialization Counter := TCounter.Create; finalization Counter.Free; end. ``` ロック / 解除も単に `Enter()` / `Exit()` を呼び出すだけで行えます。 ```pascal procedure TMyThread.Execute; begin System.TMonitor.Enter(Counter); try var v := Counter.FCounter; Inc(v); TThread.Sleep(20); Counter.FCounter := v; finally System.TMonitor.Exit(Counter) end; end; ``` :::note info モニタの概念からすると、レコードではなく抽象クラスで実装され、サブクラス化して使うようになっていれば名前と機能が一致していたと思うのですが...。 ::: VCL フォームアプリケーションで `TMonitor` を使う際には `System.TMonitor` のように修飾しないとエラーになります。VCL にも `Vcl.Forms.TMonitor` が存在するからです。 ![image.png](./images/470a5858-e428-ef81-09bd-39df9a828ad0.png) この問題を解決するために `System.MonitorEnter()` および `System.MonitorExit()` ルーチンが用意されています。 ```pascal procedure TMyThread.Execute; begin MonitorEnter(Counter); try var v := Counter.FCounter; Inc(v); TThread.Sleep(20); Counter.FCounter := v; finally MonitorExit(Counter) end; end; ``` :::note warn TMonitor は XE4 以前でパフォーマンスの問題を抱えています。 ::: **See also:** - [System.TMonitor (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.TMonitor) - [スレッド管理ルーチン (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E7%AE%A1%E7%90%86%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3) - [Monitor クラス (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.monitor) ## 2.4. TSpinLock `System.SyncObjs` で定義されている `TSpinLock` レコード [^4] は .NET の `SpinLock 構造体`と互換性があります。 いわゆる **Guarded Suspension** です。意訳すると **"駅前ロータリーロック"** です。電車が到着して、もうすぐ彼女 (or 彼氏) が駅から出てくるはずなのでロータリーを車でグルグル回って待つという手法です。一本後の電車に乗ったとかだとこの待ち合わせは失敗します。ロータリーを回っていられるのはごく短い時間だけです。 理屈的にスピンロックをロックの最初の候補として利用すべきではなく、他の方法で正しく動作している場合のパフォーマンスチューニング手段だと考えた方がいいように思います。 **See also:** - [System.System.SyncObjs.TSpinLock (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TSpinLock) - [スピンロック (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%94%E3%83%B3%E3%83%AD%E3%83%83%E3%82%AF) - [SpinLock 構造体 (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.spinlock) - [Guarded suspension (Wikipedia: en)](https://en.wikipedia.org/wiki/Guarded_suspension) - [「Java言語で学ぶデザインパターン入門マルチスレッド編」を Delphi に移植してみた (ScriptBrowserK)](http://koz.scriptbrowserk.com/Delphi/ThreadPattern.html) ## 2.5. TInterlocked `System.SyncObjs` で定義されている `TInterlocked` クラス [^5] では、値のインクリメントやデクリメント、値の交換などを Critical Section を使うことなく簡単に行えます。.NET の `Interlocked クラス`と互換性があります。 Delphi の `TInterlocked` クラスには次のようなクラスメソッドがあります。 | メソッド | 説明 | |:---|:---| | [Add()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Add) | パラメータに指定された整数型の値を加算し、その結果を返します。 | | [BitTestAndClear()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.BitTestAndClear) | 変数の一定のビットが 1 に、そしてその値が 0 に設定されているかどうかをテストします。 | | [BitTestAndSet()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.BitTestAndSet) | 変数の一定のビットが 1 に、そしてその値が 1 に設定されているかどうかをテストします。 | | [Increment()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Increment) | パラメータに指定された整数型の値をインクリメントし、その結果を返します。 | | [Decrement()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Decrement) | パラメータに指定された整数型の値をデクリメントし、その結果を返します。 | | [Exchange()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Exchange) | 指定されたパラメータの値を交換し、元の値を返します。 | | [CompareExchange()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.CompareExchange) | Target と Comparand のコンテンツを比較し、同じ場合には、Target のコンテンツを Value 値に変更します。 | | [Read()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Read)| パラメータに指定された Int64 型変数の値を返します。 | カウンタとして使うのがとても簡単です。 ```pascal ... uses ..., System.SyncObjs; ... var Counter: Int64; ... procedure TMyThread.Execute; begin var v1 := TInterlocked.Read(Counter); var v2 := v1; Inc(v1); TThread.Sleep(20); TInterlocked.Add(Counter, v1 - v2); // 差分を加算 end; ``` **See also:** - [System.System.SyncObjs.TInterlocked (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked) - [Interlocked クラス (docs.microsoft.com) ](https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.interlocked) - [インタロック変数アクセス (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/windows/win32/sync/interlocked-variable-access) ### 2.5.1. アトミック関数 `TInterlocked` のクラスメソッドのいくつかはアトミック組み込み関数のラッパーです。 | TInterlocked | アトミック関数 | |:---|:---| | [Add()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Add)
[Increment()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Add) | [System.AtomicIncrement()](https://docwiki.embarcadero.com/Libraries/ja/System.AtomicIncrement) | | [Decrement()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Decrement) | [System.AtomicDecrement()](https://docwiki.embarcadero.com/Libraries/ja/System.AtomicDecrement) | | [Exchange()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.Exchange) | [System.AtomicExchange()](https://docwiki.embarcadero.com/Libraries/ja/System.AtomicExchange) | | [CompareExchange()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TInterlocked.CompareExchange) | [System.AtomicCmpExchange()](https://docwiki.embarcadero.com/Libraries/ja/System.AtomicCmpExchange) | **See also:** - [アトミック関数 (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/windows/win32/direct3d11/direct3d-11-advanced-stages-cs-atomic-functions) ## 2.6. TMultiReadExclusiveWriteSynchronizer `System.SysUtils` で定義されている `TMultiReadExclusiveWriteSynchronizer` クラス [^6] は **複数読み出し排他的書き込みシンクロナイザ (MREWS)** とよばれます。名前が長いので `TMREWSync` というエイリアスも用意されています [^7]。 いわゆる **Readers–writer lock** です。意訳すると **"生徒-先生ロック"** です。板書するのは先生だけで、生徒は黒板を見てるだけです。黒板を見るのは生徒が同時に行えますが (Multi-read)、板書は先生だけしか行えません。実際の所、先生は複数人居てもいいのですが、板書できる先生は一人だけです (Exclusive-write)。先生たちが同時に板書する事はできません。 理屈的に`生徒 (Readers)` と`先生 (Writer)` のスレッドは分離する必要があります。 古くからあるのにあまり使われていないのは、(Delphi の場合) 機構が複雑になる割に Critical Section よりもパフォーマンスが劣る事が多いからだと思います。 **See also:** - [System.SysUtils.TMultiReadExclusiveWriteSynchronizer (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TMultiReadExclusiveWriteSynchronizer) - [複数読み出し排他的書き込みシンクロナイザを使用する](https://docwiki.embarcadero.com/RADStudio/ja/%E8%A4%87%E6%95%B0%E8%AA%AD%E3%81%BF%E5%87%BA%E3%81%97%E6%99%82%E3%81%AE%E6%8E%92%E4%BB%96%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF%E3%82%B7%E3%83%B3%E3%82%AF%E3%83%AD%E3%83%8A%E3%82%A4%E3%82%B6%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B) - [Readers–writer lock (Wikipedia: en)](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock) ## 2.7. TSimpleRWSync `System.SysUtils` で定義されている `TSimpleRWSync` クラス [^8] は、古い Delphi だと CriticalSection API のラッパーで、現在の Delphi だと `TMonitor` のラッパーです。 `BeginRead()` と `BeginWrite()`、`EndRead()` と `EndWrite()` はそれぞれ同じ動作になります。つまりは Critical Section であって、Readers–writer lock ではありません。 `TMultiReadExclusiveWriteSynchronizer` は Windows 以外のプラットフォームだと `TSimpleRWSync` のエイリアスになっています。 **See also:** - [System.SysUtils.TSimpleRWSync (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TSimpleRWSync) ## 2.8. TLightweightMREW `System.SyncObjs` で定義されている `TLightweightMREW` レコード [^9] は `TMultiReadExclusiveWriteSynchronizer ` よりも高速で軽量な新しい Readers–writer lock の実装です。 `TLightweightMREW` は、各プラットフォームにおける Readers–writer lock のネイティブ実装をラッピングしています。例えば Windows の場合には **SRW ロック** [^10] のラッパーとなっています。 **See also:** - [System.SyncObjs.TLightweightMREW (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TLightweightMREW) - [10.4.1 の新機能: 新しい TLightweightMREW レコード (blogs.embarcadero.com)](https://blogs.embarcadero.com/ja/new-tlightweightmrew-record-in-rad-studio-10-4-1-ja/) - [Slim Reader/Writer (SRW) Locks (docs.microsoft.com)](https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks) ## 2.9. TThreadList `TList` をスレッドで使うには次のようにクリティカルセクションを使います。 ```pascal:unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, SyncObjs; type TMyThread = class(TThread) private { Private 宣言 } protected procedure Execute; override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private procedure MyThreadTerminate(Sender: TObject); { Private 宣言 } public { Public 宣言 } end; const NUM_OF_THREAD = 100; var Form1: TForm1; TermCount: Integer; List: TList; CriticalSection: TCriticalSection; implementation {$R *.dfm} { TMyThread } procedure TMyThread.Execute; var v: Integer; begin CriticalSection.Acquire; try v := Integer(List[0]); Inc(v); Sleep(20); List[0] := TObject(v); finally CriticalSection.Release; end; end; procedure TForm1.MyThreadTerminate(Sender: TObject); begin Inc(TermCount); // 終了したスレッドをカウント if TermCount = NUM_OF_THREAD then // すべてのスレッドが終了したら begin ShowMessage(IntToStr(Integer(List[0]))); // Counter の値をダイアログで表示 Button1.Enabled := True; end; end; procedure TForm1.Button1Click(Sender: TObject); var i: Integer; begin Button1.Enabled := False; List[0] := TObject(0); TermCount := 0; for i := 1 to NUM_OF_THREAD do with TMyThread.Create(True) do begin FreeOnTerminate := True; OnTerminate := MyThreadTerminate; Resume; end; end; initialization List := TList.Create; List.Add(TObject(0)); CriticalSection := TCriticalSection.Create; finalization List.Free; CriticalSection.Free; end. ``` これを `TThreadList` [^11] を使って書く事もできます。 ```pascal:unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMyThread = class(TThread) private { Private 宣言 } protected procedure Execute; override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private procedure MyThreadTerminate(Sender: TObject); { Private 宣言 } public { Public 宣言 } end; const NUM_OF_THREAD = 100; var Form1: TForm1; TermCount: Integer; ThreadList: TThreadList; implementation {$R *.dfm} { TMyThread } procedure TMyThread.Execute; var v: Integer; begin with ThreadList.LockList do try v := Integer(Items[0]); Inc(v); Sleep(20); Items[0] := TObject(v); finally ThreadList.UnlockList; end; end; procedure TForm1.MyThreadTerminate(Sender: TObject); begin Inc(TermCount); // 終了したスレッドをカウント if TermCount = NUM_OF_THREAD then // すべてのスレッドが終了したら begin with ThreadList.LockList do try ShowMessage(IntToStr(Integer(Items[0]))); // Counter の値をダイアログで表示 finally ThreadList.UnlockList; end; Button1.Enabled := True; end; end; procedure TForm1.Button1Click(Sender: TObject); var i: Integer; LList: TList; begin Button1.Enabled := False; LList := ThreadList.LockList; try LList[0] := TObject(0); finally ThreadList.UnlockList; end; TermCount := 0; for i := 1 to NUM_OF_THREAD do with TMyThread.Create(True) do begin FreeOnTerminate := True; OnTerminate := MyThreadTerminate; Resume; end; end; initialization ThreadList := TThreadList.Create; ThreadList.Duplicates := dupAccept; // リストの重複を許可 ThreadList.Add(TObject(0)); finalization ThreadList.Free; end. ``` `TThreadList` には `Add()`、`Clear()`、`Remove()` メソッドがあり、項目の追加と削除は直接行えますが、参照と変更を直接行う事はできません。参照と変更は `LockList()` メソッドで一旦 TList に取り出してから行います。 ```delphi List := ThreadList.LockList; try // リストの操作 List[0] := ... List.Items[0] := ... finally ThreadList.UnlockList; end; ``` **with** 文を使うと変数を宣言する事なく短く書けます。 ```delphi with ThreadList.LockList do try // リストの操作 Items[0] := ... finally ThreadList.UnlockList; end; ``` `TThreadList` は古くからあるクラスなので古いコードの書き方をしましたが、ジェネリック版の `TThreadList` を使えばいくらかスマートに書けます。 ```pascal:unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Generics.Collections; type TMyThread = class(TThread) private { Private 宣言 } protected procedure Execute; override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private procedure MyThreadTerminate(Sender: TObject); { Private 宣言 } public { Public 宣言 } end; const NUM_OF_THREAD = 100; var Form1: TForm1; TermCount: Integer; ThreadList: TThreadList; implementation {$R *.dfm} { TMyThread } procedure TMyThread.Execute; begin with ThreadList.LockList do try var v := Items[0]; Inc(v); Sleep(20); Items[0] := v; finally ThreadList.UnlockList; end; end; procedure TForm1.MyThreadTerminate(Sender: TObject); begin Inc(TermCount); // 終了したスレッドをカウント if TermCount = NUM_OF_THREAD then // すべてのスレッドが終了したら begin with ThreadList.LockList do try ShowMessage(Items[0].ToString); // Counter の値をダイアログで表示 finally ThreadList.UnlockList; end; Button1.Enabled := True; end; end; procedure TForm1.Button1Click(Sender: TObject); begin Button1.Enabled := False; with ThreadList.LockList do try Items[0] := 0; finally ThreadList.UnlockList; end; TermCount := 0; for var i := 1 to NUM_OF_THREAD do with TMyThread.Create(True) do begin FreeOnTerminate := True; OnTerminate := MyThreadTerminate; Start; end; end; initialization ThreadList := TThreadList.Create; ThreadList.Duplicates := dupAccept; // リストの重複を許可 ThreadList.Add(0); finalization ThreadList.Free; end. ``` **See also:** - [System.Classes.TThreadList (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThreadList) ## 2.10. Lock() / Unlock() `TCanvas` およびその派生クラスには、キャンバスにアクセスするためのクリティカルセクションを設定する `Lock()` [^12] / `Unlock()` [^12] メソッドがあります。 他にもクリティカルセクションを設定するための用途で `Lock()` / `Unlock()` メソッドを持つクラスがあります。 **See also:** - [Vcl.Graphics.TCustomCanvas.Lock (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/Vcl.Graphics.TCustomCanvas.Lock) - [Vcl.Graphics.TCustomCanvas.UnLock (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/Vcl.Graphics.TCustomCanvas.Unlock) - [Lock() を持つクラス (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/index.php?search=Lock+%E3%82%AF%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AB%E3%83%AB+%E3%82%BB%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3&title=%E7%89%B9%E5%88%A5%3A%E6%A4%9C%E7%B4%A2&profile=default&fulltext=1) - [オブジェクトをロックする (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E3%83%AD%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B) # 参考 - [Delphi で Singleton パターンを実装する (Monitor 版) (Owl's perspective)](http://owlsperspective.blogspot.com/2011/04/singleton-pattern-in-delphi-3.html) - [詳説!DataSnap 2010 (第15回 エンバカデロ・デベロッパーキャンプ【A5】)](https://community.embarcadero.com/images/40256/a5.pdf) - [Multithreading - The Delphi Way. (Martin Harvey)](https://seti.net/engineering/threads/threads.php) - [THttpClient の落とし穴2 (Qiita: @pik)](https://qiita.com/pik/items/1361696efeb4348f2acc) - [[C++Builder] TMultiReadExclusiveWriteSynchronizerでRead-Write Lockパターン (山本隆の開発日誌)](https://www.gesource.jp/weblog/?p=4752) - [.NETマルチスレッド・プログラミング入門 (@IT)](https://atmarkit.itmedia.co.jp/ait/subtop/features/dotnet/mthread_index.html) - [Readers-writer lock - Part 1: Why? (The Delphi Geek)](https://www.thedelphigeek.com/2020/11/readers-writer-lock-part-1-why.html) - [Readers-writer lock - Part 2: Implementation (The Delphi Geek)](https://www.thedelphigeek.com/2020/12/readers-writer-lock-part-2-impleme.html) - [Readers-writer lock - Part 3: Some numbers (The Delphi Geek)](https://www.thedelphigeek.com/2021/02/readers-writer-lock-part-3-some-numbers.html) - [Readers-writer lock - Part 4: Improving TLightweightMREW (The Delphi Geek)](https://www.thedelphigeek.com/2021/02/readers-writ-47358-48721-45511-46172.html) - [TThreadListの使い方 (山本隆の開発日誌)](https://www.gesource.jp/weblog/?p=6647) # 索引 [ [← 1. スレッドオブジェクト](./39da99a10202589e002c.md) ] [ [↑ 目次へ](./e8c1ff3a4c74e4c2a4f3.md) ] [ [→ 3. スレッドローカル変数 (スレッドローカルストレージ)](./09f04b5768135401eebb.md) ] [^1]: `TCriticalSection` は Delphi 3 以降で利用可能です。 [^2]: `TRTLCriticalSectionHelper` は Delphi 2009 以降で利用可能です。 [^3]: `TMonitor` は Delphi 2009 以降で利用可能です。 [^4]: `TSpinLock` は Delphi XE 以降で利用可能です。 [^5]: `TInterlocked` は Delphi XE 以降で利用可能です。 [^6]: `TMultiReadExclusiveWriteSynchronizer` は Delphi 4 以降で利用可能です。 [^7]: `TMREWSync` は Delphi 6 以降で利用可能です。 [^8]: `TSimpleRWSync` は Delphi 6 以降で利用可能です。 [^9]: `TLightweightMREW` は Delphi 10.4.1 Sydney 以降で利用可能です。 [^10]: `SRW ロック` は Windows Vista 以降で利用可能です。 [^11]: `TThreadList` は Delphi 3 以降で利用可能です。 [^12]: `Lock()` や `Unlock()` は Delphi 3 以降で利用可能です。