モニタとは

モニタ(Monitor)とは、任意のオブジェクトに対するアクセスを同期するための排他処理の概念の一つ。 クリティカルセクションに対する排他制御を行う目的で使用する。

モニタは、同期対象のオブジェクトに対するロックの取得と解放の二つの操作を提供する。 モニタは、同期対象のオブジェクトがロックされている間は、そのロックが解放されるまでは他のスレッドに対してオブジェクトへのアクセスをブロックする。 ロックは取得したスレッドが解放しなければならない。 この動作により、クリティカルセクションにおける排他処理を実現し、同期対象のオブジェクトに対する要求が競合しないように保護することができる。

.NET FrameworkにおけるMonitor

Monitorクラスは、インスタンスを生成することはできない。 同期対象のオブジェクト(同期オブジェクト)に対するロックの取得と解放は、MonitorクラスのstaticメソッドEnter()およびExit()を用いて行う。

C#のlock構文(VB.NETではSyncLock構文)は、コンパイル時にMonitorクラスを用いたコードに展開される。 すなわち、

lock (syncobj)
{
   ...
}

というlock構文を用いたコードは、

Monitor.Enter(syncobj);

try {
  ...
}
finally {
  Monitor.Exit(syncobj);
}

というMonitorクラスを用いたコードとほぼ等価となる。

主なメソッド

static void Enter(object)
任意の同期オブジェクトに対するロックを取得し、クリティカルセクションに入る。
static void Exit(object)
任意の同期オブジェクトに対するロックを解放し、クリティカルセクションから出る。

主な呼び出し順序

  1. Monitor.Enter()
  2. Monitor.Exit()

使用例

Monitorを用いてクリティカルセクションに対する排他制御を行い、クリティカルセクションに対して複数のスレッドから同時にアクセスを試みる。

using System;
using System.Threading;

class Sample {
  static object syncobj = null;

  static void Main()
  {
    // 同期対象のオブジェクト
    syncobj = new object();

    // スレッドを5本生成
    for (int i = 0; i < 5; i++) {
      Thread t = new Thread(CriticalSection);

      t.Start();
    }

    // 生成したスレッドの終了を待機する。
    Thread.Sleep(750);
  }

  private static void CriticalSection()
  {
    Console.WriteLine("ThreadID#{0:X8}: Waiting", Thread.CurrentThread.ManagedThreadId);

    Monitor.Enter(syncobj);

    try {
      Console.WriteLine("ThreadID#{0:X8}: Enter", Thread.CurrentThread.ManagedThreadId);

      Thread.Sleep(100);
    }
    finally {
      Monitor.Exit(syncobj);

      Console.WriteLine("ThreadID#{0:X8}: Exit", Thread.CurrentThread.ManagedThreadId);
    }
  }
}
Imports System
Imports System.Threading

Class Sample
  Shared syncobj As Object = Nothing

  Shared Sub Main()
    ' 同期対象のオブジェクト
    syncobj = New Object()

    ' スレッドを5本生成
    For i As Integer = 1 To 5
      Dim t As New Thread(AddressOf CriticalSection)

      t.Start()
    Next

    ' 生成したスレッドの終了を待機する。
    Thread.Sleep(750)
  End Sub

  Private Shared Sub CriticalSection()
    Console.WriteLine("ThreadID#{0:X8}: Waiting", Thread.CurrentThread.ManagedThreadId)

    Monitor.Enter(syncobj)

    Try
      Console.WriteLine("ThreadID#{0:X8}: Enter", Thread.CurrentThread.ManagedThreadId)

      Thread.Sleep(100)
    Finally
      Monitor.Exit(syncobj)

      Console.WriteLine("ThreadID#{0:X8}: Exit", Thread.CurrentThread.ManagedThreadId)
    End Try
  End Sub
End Class
実行結果
ThreadID#00000006: Waiting
ThreadID#00000004: Waiting
ThreadID#00000004: Enter
ThreadID#00000007: Waiting
ThreadID#00000003: Waiting
ThreadID#00000005: Waiting
ThreadID#00000004: Exit
ThreadID#00000003: Enter
ThreadID#00000003: Exit
ThreadID#00000007: Enter
ThreadID#00000007: Exit
ThreadID#00000005: Enter
ThreadID#00000005: Exit
ThreadID#00000006: Enter
ThreadID#00000006: Exit

上記例のCriticalSection()メソッドを次のように記述しても同様の結果となる。

lock構文を用いて記述した例
private void CriticalSection()
{
  Console.WriteLine("ThreadID#{0:X8}: Waiting", Thread.CurrentThread.ManagedThreadId);

  lock(syncobj)
  {
    Console.WriteLine("ThreadID#{0:X8}: Enter", Thread.CurrentThread.ManagedThreadId);

    Thread.Sleep(100);
  }

  Console.WriteLine("ThreadID#{0:X8}: Exit", Thread.CurrentThread.ManagedThreadId);
}
SyncLock構文を用いて記述した例
Private Shared Sub CriticalSection()
  Console.WriteLine("ThreadID#{0:X8}: Waiting", Thread.CurrentThread.ManagedThreadId)

  SyncLock syncobj
    Console.WriteLine("ThreadID#{0:X8}: Enter", Thread.CurrentThread.ManagedThreadId)

    Thread.Sleep(100)
  End SyncLock

  Console.WriteLine("ThreadID#{0:X8}: Exit", Thread.CurrentThread.ManagedThreadId)
End Sub