<2> クリティカルセクションとロック (Delphi コンカレントプログラミング)

# 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)   メソッド /<br>[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)  メソッド　/<br>[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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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)<br>[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<Integer>;

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<Integer>.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 の落とし穴２ (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. スレッドオブジェクト](https://qiita.com/ht_deko/items/39da99a10202589e002c) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e8c1ff3a4c74e4c2a4f3) ] [ [→ 3. スレッドローカル変数 (スレッドローカルストレージ)](https://qiita.com/ht_deko/items/09f04b5768135401eebb) ] 

[^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 以降で利用可能です。