§1 ミューテックスとは

ミューテックス(Mutex = MUTual EXclusion)とは排他制御を行う上で用いられる概念のひとつ。 ミューテックスは、ミューテックス(=排他区間)へ入ることを要求するP操作、ミューテックスから出ることを通知するV操作から構成される。 ミューテックスは同時に1つの処理しか排他区間に入ることを許可しないため、カウンタの初期値が1であるセマフォと同義である。 セマフォについての詳細はSystem.Threading.Semaphoreにて解説している。

§2 .NET FrameworkにおけるMutex

基本的にはカウンタの初期値が1に固定されたSystem.Threading.Semaphoreと同じ。 名前付きでMutexインスタンスを生成すると、オペレーティングシステム全体から参照できる(プロセス間で共有できる)システムミューテックスとなり、名前なしでMutexインスタンスを生成すると、プロセス内でのみ参照できるローカルミューテックスとなる。

ミューテックスに入るにはWaitOne()、ミューテックスから出るにはReleaseMutex()を呼ぶ。 ただし、Semaphore.Release()とは異なり、ReleaseMutex()はWaitOne()したスレッドからしか呼び出すことが許可されない。 つまり、ミューテックスに入ったスレッド以外はミューテックスから出ることが出来ない(他のスレッドからは操作できない)。 ミューテックスに入っている処理(スレッド)がない場合、ミューテックスは「シグナル状態」(signaled)となる。 ミューテックスがシグナル状態になると、WaitOne()でブロックされている処理はミューテックスに入ることができるようになる。

システムミューテックスの場合ミューテックスには「所有権」という概念があり、システムミューテックスを生成したスレッドは「所有権」を持つことができる。 ミューテックスの所有権を持ったスレッドが終了した場合、ミューテックスは「放棄された状態」(abandoned)となる。 このとき、ミューテックスはシグナル状態となるが、他のスレッドがミューテックスに入ろうと待機(WaitOne)していた場合、そのスレッドではAbandonedMutexExceptionが発生する。

「所有権」の有無はインスタンスの生成時に指定できる(初期所有権)。 初期所有権を付与しなかった場合は、システムミューテックスを生成したスレッドが終了した後に他のスレッドが放棄されたミューテックスに入ろうとしても、AbandonedMutexExceptionは発生しない。

§3 主なコンストラクタ

Mutex()
名前なしミューテックスのインスタンスを生成する。
Mutex(bool initiallyOwned, string name)
初期所有権の有無、ミューテックスの名前を指定してインスタンスを生成する。
Mutex(bool initiallyOwned, string name, out bool createdNew)
初期所有権の有無、ミューテックスの名前を指定してインスタンスを生成する。
新しく名前付きミューテックスを作成した場合はcreatedNewにtrue、すでに同名の名前付きミューテックスが存在している場合はfalseがセットされる。

§4 主なメソッド

static Mutex OpenExisting(string name)
すでに作成されている名前付きミューテックスを取得する。 取得できない場合、存在しない場合は、WaitHandleCannotBeOpenedExceptionがスローされる。
bool WaitOne()
ミューテックスに入る(P操作)。 すでに他のスレッドがミューテックスに入っている場合は、ReleaseMutex()が呼ばれる(V操作が行われる)まで処理がブロックされる。
void ReleaseMutex()
ミューテックスから出る(V操作)。
void Close()
インスタンスが保持しているリソースを解放する。

§5 主な呼び出し順序

ローカルミューテックスの場合。

  1. new Mutex()
  2. WaitOne()
  3. Release()
  4. Close()

システムミューテックスの場合。

  1. 新しく生成する場合はnew Mutex()、既に生成されたものを参照する場合はMutex.OpenExisting()
  2. WaitOne()
  3. Release()
  4. Close()

§6 使用例

システムミューテックスを生成し、排他区間に対して複数のプロセスから同時にアクセスを試みる。

using System;
using System.Threading;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    var processId = Process.GetCurrentProcess().Id;

    Console.WriteLine("{0} started", processId);

    // プロセス間で共有する名前付きミューテックスを作成(初期所有権は付与しない)
    const string mutexName = "named-mutex-sample";
    bool createdNew;

    using (var mutex = new Mutex(false, mutexName, out createdNew)) {
      // このプロセスで名前付きミューテックスを生成した
      if (createdNew)
        Console.WriteLine("{0} mutex created", processId);

      Console.WriteLine("{0} waiting", processId);

      // ミューテックスに入る (入れるまで処理をブロックする)
      mutex.WaitOne();

      Console.WriteLine("{0} -> enter", processId);

      // 完了するまで時間のかかる処理を想定
      Thread.Sleep((new Random(processId)).Next(100, 500));

      // ミューテックスを出る
      mutex.ReleaseMutex();

      Console.WriteLine("{0} <- exited", processId);
    }
  }
}

上記プログラムを5プロセス同時に実行した場合の動作例。

Windows + .NET Frameworkでの動作結果例
>csc test.cs
>test.exe >&2 | test.exe >&2 | test.exe >&2 | test.exe >&2 | test.exe
2328 started
2328 mutex created
2328 waiting
2328 -> enter
3952 started
3720 started
3952 waiting
3720 waiting
4624 started
4624 waiting
4940 started
4940 waiting
2328 <- exited
4940 -> enter
3720 -> enter
4940 <- exited
4624 -> enter
3720 <- exited
4624 <- exited
3952 -> enter
3952 <- exited

Monoでは、環境変数MONO_ENABLE_SHMをセットして共有メモリの使用を有効にしないと名前付きミューテックスを使用できない。

Linux + Monoでの動作結果例
$ mcs test.cs && for i in `seq 1 5` ; do MONO_ENABLE_SHM=1 mono test.exe & done

22269 started
22269 mutex created
22269 waiting
22269 -> enter
22267 started
22267 waiting
22265 started
22265 waiting
22268 waiting
22266 started
22266 waiting
22269 <- exited
22267 -> enter
22267 <- exited
22268 -> enter
22268 <- exited
22265 -> enter
22265 <- exited
22266 -> enter
22266 <- exited