<6> ミューテックス (同期オブジェクト) (Delphi コンカレントプログラミング)

# 6. ミューテックス (同期オブジェクト)

**ミューテックス (Mutex)** は同期オブジェクトの一つです。「相互排他 (**mut**ual **ex**clusion)」の略語であり造語です。

## 6.1. 同期オブジェクト

同期オブジェクトとは、同じプロセス (または異なるプロセス) のスレッド間でシステムリソースのアクセスを制御する機構の事です。

待機中のスレッドは同期オブジェクトが**非シグナル状態** (ビジー) の状態では処理を続行できません。同期オブジェクトが**シグナル状態**になると処理を続行できるようになります。

プロセス間の同期オブジェクトは`システム同期オブジェクト`、同一プロセスでの同期オブジェクトは`ローカル同期オブジェクト`という事になります。

同期オブジェクトに **"Light-weight～"** と言う名前が付いていたら、それはローカル同期オブジェクト (マルチスレッド用途) だと思って間違いありません。

## 6.2. TMutex

`TMutex` クラス [^1] を使うと、一つのスレッドからしかアクセスできない機構を作る事ができます。`TMutex` は `System.SyncObjs` で定義されています。

最初の書式のコンストラクタでは無名ミューテックスを作成します。無名ミューテックスはローカルミューテックスで、Critical Section と同等の動作になります。ミューテックスの作成に失敗すると `EOSError` が発生します。

二番目の書式のコンストラクタでは、名前付きのミューテックスを作成します (名前が空でない場合はシステムミューテックス)。ミューテックスの作成に失敗すると `EOSError` が発生します。

三番目の書式のコンストラクタでは名前付きのミューテックスを開きます。ミューテックスが開けなかった場合には `EOSError` が発生します。Windows 環境でない場合には名前付きのミューテックスを作成します。

```pascal
constructor Create(UseCOMWait: Boolean = False);
constructor Create(MutexAttributes: PSecurityAttributes; InitialOwner: Boolean; const Name: string; UseCOMWait: Boolean = False);
constructor Create(DesiredAccess: Cardinal; InheritHandle: Boolean; const Name: string; UseCOMWait: Boolean = False);
```

最初期のこのクラスは Windows API をラップしたものでした。

| Delphi | Windows API |
|:---|:---|
| [TMutex.Create()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TMutex.Create) コンストラクタ | [CreateMutexA()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexa) / [CreateMutexW()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw)|
| [TMutex.Create()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TMutex.Create) コンストラクタ | [OpenMutexW()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-openmutexw)|
| [TMutex.Release()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TMutex.Release) メソッド | [ReleaseMutex()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-releasemutex) |

Critical Section としての `TMutex` の使い方は次のようになります。

```pascal
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;
  Mutex: TMutex; // 追加

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
  inherited;
  Mutex.Acquire;
  try
    var v := Counter;
    Inc(v);
    Mutex.WaitFor(20); // Sleep() の代わりに
    Counter := v;
  finally
    Mutex.Release;
  end;
end;

initialization
  Mutex := TMutex.Create;
finalization
  Mutex.Free;
end.
```

`Acquire()` メソッドでミューテックスが解放されるまで待って、排他ロックを取得します。`Release()` メソッドでミューテックスを解放します。`Sleep()` の代わりに `WaitFor()` を使う事もできます。

Critical Section としては、`TCriticalSection` に比べると相当遅いです。

**See also:**

 - [TMutex (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TMutex)
 - [System.SyncObjs.TMutex.Create (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TMutex.Create)
 - [System.SyncObjs.TMutex.Acquire (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TMutex.Acquire)
 - [System.SyncObjs.TMutex.Release (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TMutex.Release)
 - [System.SyncObjs.THandleObject.WaitFor (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.THandleObject.WaitFor)

## 6.2.1. TMutex を使ったアプリケーションの二重起動防止
ちょっとマルチスレッドからは離れますが、プロセス間同期オブジェクト (システムミューテックス) としての使い方です。

以前からミューテックスを使った二重起動防止ロジックはありましたが、`TMutex` を使って次のように書くことができます。

```pascal:Project1.dpr
program Project1;

uses
  Vcl.Forms,
  System.SysUtils,
  System.SyncObjs,
  WinAPI.Windows,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

const
  MUTEX_NAME = 'Global\MyApp';

begin
  Application.Initialize;
  var MutexExists: Boolean := True;
  var oMutex: TMutex;
  try
    oMutex := TMutex.Create(MUTEX_ALL_ACCESS, False, MUTEX_NAME);
  except
    on E: EOSError do
      MutexExists := False;
  end;
  if MutexExists then
    begin
      Application.MessageBox('二重起動だよ！', 'エラー', MB_ICONERROR or MB_OK);
    end
  else
    begin
      var cMutex := TMutex.Create(nil, False, MUTEX_NAME);
      try
        Application.MainFormOnTaskbar := True;
        Application.CreateForm(TForm1, Form1);
        Application.Run;
      finally
        cMutex.Free;
      end;
    end;
  FreeAndNil(oMutex);
end.
```

素の Windows API を使った方が簡単な気もします。

**See also:**

 - [二重起動を防止 - FDELPHI Delphi FAQ (delfusa.main.jp)](http://delfusa.main.jp/delfusafloor/archive/www.nifty.ne.jp_forum_fdelphi/faq/00022.htm)
 - [[Delphi] アプリケーションの多重起動を防止する (Win32) (くろねこ研究所)](https://www.blackcat.xyz/article.php/ProgramingFAQ_del0008)

#参考

 - [ミューテックス (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%83%9F%E3%83%A5%E3%83%BC%E3%83%86%E3%83%83%E3%82%AF%E3%82%B9)

# 索引
[ [← 5. サードパーティ製ライブラリ](https://qiita.com/ht_deko/items/e7f655bb73af05fbac35) ] [ [↑ 目次へ](https://qiita.com/items/e8c1ff3a4c74e4c2a4f3) ] [ [→ 7. セマフォ (同期オブジェクト)](https://qiita.com/ht_deko/items/6d7aa5ab5856d231b79e) ]

[^1]: `TMutex` は Delphi 2005 以降で利用可能です。