6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DelphiAdvent Calendar 2021

Day 13

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

Last updated at Posted at 2021-12-12

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

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

6.1. 同期オブジェクト

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

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

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

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

6.2. TMutex

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

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

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

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

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() コンストラクタ CreateMutexA() / CreateMutexW()
TMutex.Create() コンストラクタ OpenMutexW()
TMutex.Release() メソッド ReleaseMutex()

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

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:

6.2.1. TMutex を使ったアプリケーションの二重起動防止

ちょっとマルチスレッドからは離れますが、プロセス間同期オブジェクト (システムミューテックス) としての使い方です。

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

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:

#参考

索引

[ ← 5. サードパーティ製ライブラリ ] [ ↑ 目次へ ] [ → 7. セマフォ (同期オブジェクト) ]

  1. TMutex は Delphi 2005 以降で利用可能です。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?