┏第22号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜メニュー ■.NET Tips ・.NETのマルチスレッドプログラミング その4 - ManualResetEvent、AutoResetEvent、Mutexクラスの使い方 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜メニュー ─────────────────────────────── ■.NET Tips ─────────────────────────────── ●.NETのマルチスレッドプログラミング その4 ★ManualResetEvent、AutoResetEvent、Mutexクラスの使い方 ManualResetEvent、AutoResetEvent及びMutexクラスは、WaitHandleク ラスから派生したクラスで、複数のスレッド間で同期をとるために同 期オブジェクトとして使用されます。 これらのクラスのオブジェクト(「待機ハンドル」と呼ばれる)を使 用して、あるスレッドの状態を別のスレッドに通知することができま す。待機ハンドルの状態には「シグナル状態」と「非シグナル状態」 の2つがあり、待機ハンドルをどのスレッドも所有していなければ「シ グナル状態」、所有していれば「非シグナル状態」です。 スレッドが待機ハンドルの所有を要求するには、待機メソッド( WaitHandle.WaitOneなど)を呼び出します。待機メソッドは、待機ハ ンドルが非シグナル状態になるまで(すなわち、待機ハンドルを所有 するスレッドが待機ハンドルを解放するまで)スレッドをブロックしま す。待機ハンドルを所有するスレッドで待機ハンドルをシグナル状態 にする(すなわち、待機ハンドルを解放する)ことにより、ブロック を解除します。この仕組みを利用することにより、スレッドの同期を 図ります。 (補足:ただし、これらのクラスはWin32同期ハンドルをカプセル化し たものですので、Monitorクラスと比較すると移植性に劣るという欠点 があります。) ☆ManualResetEvent、AutoResetEventクラス ManualResetEvent、AutoResetEventオブジェクトは、「同期イベント」 または「イベントオブジェクト」などと呼ばれています。(ここで言 う「同期イベント」と、よく使われる「イベント」とを混同しないよ うに注意してください。) はじめに、同期イベントを使ってスレッドの同期を行う基本的な方法 を紹介します。まずブロックさせたいスレッドで、非シグナル状態の 同期イベントのWaitOneメソッドを呼び出し、スレッドを待機させます。 スレッドをブロックする必要がなくなったところで、別のスレッドで Setメソッドを呼び出し、同期イベントをシグナル状態にして、ブロッ クを解除します。 次のコードでは、ManualResetEvent.WaitOneメソッドによりメインス レッドをブロックし、もう一つのスレッドがManualResetEvent.Setメ ソッドを呼び出すまで待機しています。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 'Imports System.Threading 'が宣言されているものとする Public Shared manualEvent As ManualResetEvent 'エントリポイント Public Shared Sub Main() '非シグナル状態でManualResetEventオブジェクトを作成 manualEvent = New ManualResetEvent(False) 'スレッドを作成し、開始する Dim t1 As New Thread(New ThreadStart(AddressOf MyMethod)) t1.Name = "1" t1.Start() 'シグナル状態になるまでスレッドをブロックする manualEvent.WaitOne() Console.WriteLine("メインスレッド終了") Console.ReadLine() End Sub Public Shared Sub MyMethod() Console.WriteLine("{0}:スレッド開始", Thread.CurrentThread.Name) '何か仕事をするものとする Thread.Sleep(1000) 'シグナル状態にする manualEvent.Set() Console.WriteLine("{0}:スレッド終了", Thread.CurrentThread.Name) End Sub '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //using System.Threading; //が宣言されているものとする public static ManualResetEvent manualEvent; //エントリポイント public static void Main() { //非シグナル状態でManualResetEventオブジェクトを作成 manualEvent = new ManualResetEvent(false); //スレッドを作成し、開始する Thread t1 = new Thread(new ThreadStart(MyMethod)); t1.Name = "1"; t1.Start(); //シグナル状態になるまでスレッドをブロックする manualEvent.WaitOne(); Console.WriteLine("メインスレッド終了"); Console.ReadLine(); } public static void MyMethod() { Console.WriteLine("{0}:スレッド開始", Thread.CurrentThread.Name); //何か仕事をするものとする Thread.Sleep(1000); //シグナル状態にする manualEvent.Set(); Console.WriteLine("{0}:スレッド終了", Thread.CurrentThread.Name); } //・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 次にManualResetEventを使って、2つのスレッドを交互に実行する例を 示します。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 'Imports System.Threading 'が宣言されているものとする Public Shared manualEvent1 As ManualResetEvent Public Shared manualEvent2 As ManualResetEvent 'エントリポイント Public Shared Sub Main() '非シグナル状態でManualResetEventオブジェクトを作成 manualEvent1 = New ManualResetEvent(False) manualEvent2 = New ManualResetEvent(False) 'スレッドを作成し、開始する Dim t1 As New Thread(New ThreadStart(AddressOf MyMethod1)) t1.Name = "1" Dim t2 As New Thread(New ThreadStart(AddressOf MyMethod2)) t2.Name = "2" t1.Start() t2.Start() manualEvent1.Set() Console.ReadLine() End Sub Public Shared Sub MyMethod1() Dim i As Integer For i = 0 To 99 'manualEvent1がシグナル状態になるまでスレッドをブロック manualEvent1.WaitOne() 'manualEvent1を非シグナル状態にする manualEvent1.Reset() Console.Write(Thread.CurrentThread.Name) 'manualEvent2をシグナル状態にする manualEvent2.Set() Next i End Sub Public Shared Sub MyMethod2() Dim i As Integer For i = 0 To 99 'manualEvent2がシグナル状態になるまでスレッドをブロック manualEvent2.WaitOne() 'manualEvent2を非シグナル状態にする manualEvent2.Reset() Console.Write(Thread.CurrentThread.Name) 'manualEvent1をシグナル状態にする manualEvent1.Set() Next i End Sub '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //using System.Threading; //が宣言されているものとする public static ManualResetEvent manualEvent1; public static ManualResetEvent manualEvent2; //エントリポイント public static void Main() { //非シグナル状態でManualResetEventオブジェクトを作成 manualEvent1 = new ManualResetEvent(false); manualEvent2 = new ManualResetEvent(false); //スレッドを作成し、開始する Thread t1 = new Thread(new ThreadStart(MyMethod1)); t1.Name = "1"; Thread t2 = new Thread(new ThreadStart(MyMethod2)); t2.Name = "2"; t1.Start(); t2.Start(); //manualEvent1をシグナル状態にする manualEvent1.Set(); Console.ReadLine(); } public static void MyMethod1() { for (int i = 0; i < 100; i++) { //manualEvent1がシグナル状態になるまでスレッドをブロック manualEvent1.WaitOne(); //manualEvent1を非シグナル状態にする manualEvent1.Reset(); Console.Write(Thread.CurrentThread.Name); //manualEvent2をシグナル状態にする manualEvent2.Set(); } } public static void MyMethod2() { for (int i = 0; i < 100; i++) { //manualEvent2がシグナル状態になるまでスレッドをブロック manualEvent2.WaitOne(); //manualEvent2を非シグナル状態にする manualEvent2.Reset(); Console.Write(Thread.CurrentThread.Name); //manualEvent1をシグナル状態にする manualEvent1.Set(); } } //・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //出力結果例: //1212121212121212121212121212121212121212121212121212121212121212121212 //1212121212121212121212121212121212121212121212121212121212121212121212 //121212121212121212121212121212121212121212121212121212121212 上記のコードではManualResetEvent.WaitOneメソッドの後すぐに Resetメソッドを呼び出して非シグナル状態に戻していますが、 ManualResetEventの代わりにAutoResetEventを使うことにより、この 手間が省けます。AutoResetEventでは、シグナルを待機中のスレッド がすべて解放されると、自動的に非シグナル状態にリセットされるの です。(待機中のスレッドがない場合は、無限にシグナル状態のまま となります。) 以下に上記のコードをAutoResetEventを使って書き換えた例を示します。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 'Imports System.Threading 'が宣言されているものとする Public Shared autoEvent1 As AutoResetEvent Public Shared autoEvent2 As AutoResetEvent 'エントリポイント Public Shared Sub Main() '非シグナル状態でAutoResetEventオブジェクトを作成 autoEvent1 = New AutoResetEvent(False) autoEvent2 = New AutoResetEvent(False) 'スレッドを作成し、開始する Dim t1 As New Thread(New ThreadStart(AddressOf MyMethod1)) t1.Name = "1" Dim t2 As New Thread(New ThreadStart(AddressOf MyMethod2)) t2.Name = "2" t1.Start() t2.Start() 'autoEvent1をシグナル状態にする autoEvent1.Set() Console.ReadLine() End Sub Public Shared Sub MyMethod1() Dim i As Integer For i = 0 To 99 'autoEvent1がシグナル状態になるまでスレッドをブロック autoEvent1.WaitOne() Console.Write(Thread.CurrentThread.Name) 'autoEvent2をシグナル状態にする autoEvent2.Set() Next i End Sub Public Shared Sub MyMethod2() Dim i As Integer For i = 0 To 99 'autoEvent2がシグナル状態になるまでスレッドをブロック autoEvent2.WaitOne() Console.Write(Thread.CurrentThread.Name) 'autoEvent1をシグナル状態にする autoEvent1.Set() Next i End Sub '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //using System.Threading; //が宣言されているものとする public static AutoResetEvent autoEvent1; public static AutoResetEvent autoEvent2; //エントリポイント public static void Main() { //非シグナル状態でAutoResetEventオブジェクトを作成 autoEvent1 = new AutoResetEvent(false); autoEvent2 = new AutoResetEvent(false); //スレッドを作成し、開始する Thread t1 = new Thread(new ThreadStart(MyMethod1)); t1.Name = "1"; Thread t2 = new Thread(new ThreadStart(MyMethod2)); t2.Name = "2"; t1.Start(); t2.Start(); //autoEvent1をシグナル状態にする autoEvent1.Set(); Console.ReadLine(); } public static void MyMethod1() { for (int i = 0; i < 100; i++) { //autoEvent1がシグナル状態になるまでスレッドをブロック autoEvent1.WaitOne(); Console.Write(Thread.CurrentThread.Name); //autoEvent2をシグナル状態にする autoEvent2.Set(); } } public static void MyMethod2() { for (int i = 0; i < 100; i++) { //autoEvent2がシグナル状態になるまでスレッドをブロック autoEvent2.WaitOne(); Console.Write(Thread.CurrentThread.Name); //autoEvent1をシグナル状態にする autoEvent1.Set(); } } //・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 複数の待機ハンドルのシグナル状態により待機が解除されるようにす るには、WaitOneではなく、WaitAllまたはWaitAnyメソッドを使用しま す。WaitAllメソッドでは指定されたすべての待機ハンドルがシグナル 状態になるまで待機し、WaitAnyメソッドでは指定されたいずれかの待 機ハンドルがシグナル状態になるまで待機します。 ☆Mutexクラス Mutexは同時に1つのスレッドでしか所有できない同期オブジェクトで す。 Mutexを使用して同期を行う基本的な方法は、次のようなものです。ま ずミューテックスの所有権を持たないスレッドがWaitOneメソッドを呼 び出して所有権を要求します。この時ミューテックスの所有権を持つ スレッドがなければ、このスレッドが所有権を取得しますが、別のス レッドが所有権を取得していれば、WaitOneメソッドを呼び出したスレ ッドは待機状態になります。ミューテックスを所有しているスレッド がReleaseMutexメソッドを呼び出すか、あるいは正常終了してミュー テックスの所有を解放すると、待機中の次のスレッドが所有権を取得 します。 なお、ミューテックスを所有しているスレッドがさらにWaitOneメソッ ドを繰り返した場合、ミューテックスの所有を解放するためには、 ReleaseMutexメソッドを同じだけ呼び出す必要があります。 Mutexを使用した簡単な例を次に示します。MyMethod1メソッドの Mutex.WaitOneとMutex.ReleaseMutexで囲まれた所が1つのスレッドの みが入れる同期された部分となります。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 'Imports System.Threading 'が宣言されているものとする Public Shared mut As Mutex 'エントリポイント Public Shared Sub Main() 'Mutexオブジェクトを作成(初期所有権なし) mut = New Mutex '2つのスレッドを作成し、開始する Dim t1 As New Thread(New ThreadStart(AddressOf MyMethod1)) t1.Name = "1" Dim t2 As New Thread(New ThreadStart(AddressOf MyMethod1)) t2.Name = "2" t1.Start() t2.Start() Console.ReadLine() End Sub Public Shared Sub MyMethod1() Dim i As Integer For i = 0 To 99 'ミューテックスの所有権を要求する '取得できない時は、取得できるまで待機 mut.WaitOne() '同期されたコードブロック Console.Write(Thread.CurrentThread.Name) 'Mutexを解放する mut.ReleaseMutex() Next i End Sub '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //using System.Threading; //が宣言されているものとする public static Mutex mut; //エントリポイント public static void Main() { //Mutexオブジェクトを作成(初期所有権なし) mut = new Mutex(); //2つのスレッドを作成し、開始する Thread t1 = new Thread(new ThreadStart(MyMethod1)); t1.Name = "1"; Thread t2 = new Thread(new ThreadStart(MyMethod1)); t2.Name = "2"; t1.Start(); t2.Start(); Console.ReadLine(); } public static void MyMethod1() { for (int i = 0; i < 100; i++) { //ミューテックスの所有権を要求する //取得できない時は、取得できるまで待機 mut.WaitOne(); //同期されたコードブロック Console.Write(Thread.CurrentThread.Name); //Mutexを解放する mut.ReleaseMutex(); } } //・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ Mutexの大きな特徴としてはさらに、スレッド間の同期だけではなく、 プロセス間の同期にも使用できる点があげられます。アプリケーショ ンの二重起動を防止するためにMutexを使用する方法は、「.NET Tips - 二重起動を禁止する」で紹介しています。 ・.NET Tips - 二重起動を禁止する http://dobon.net/vb/dotnet/process/checkprevinstance.html 参考: ・WaitHandle http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconwaithandle.asp ・WaitHandle クラス http://www.microsoft.com/japan/msdn/library/ja/cpref/html/frlrfsystemthreadingwaithandleclasstopic.asp ・AutoResetEvent クラス http://www.microsoft.com/japan/msdn/library/ja/cpref/html/frlrfsystemthreadingautoreseteventclasstopic.asp ・ManualResetEvent クラス http://www.microsoft.com/japan/msdn/library/ja/cpref/html/frlrfsystemthreadingmanualreseteventclasstopic.asp ・Mutex クラス http://www.microsoft.com/japan/msdn/library/ja/cpref/html/frlrfsystemthreadingmutexclasstopic.asp ・高度な同期化技法 http://www.microsoft.com/japan/msdn/library/ja/vbcn7/html/vatchAdvancedSynchronizationTechniques.asp =============================== ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  (Microsoft MVP for Visual Basic, Oct 2003-Oct 2004)  http://dobon.net  dobon_info@yahoo.co.jp ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/vb/bbs.html ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2003 DOBON! All rights reserved. ===============================