.NETプログラミング研究 第22号 †
.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メソッドを呼び出すまで待機しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| |
Public Shared manualEvent As ManualResetEvent
Public Shared Sub Main()
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| |
public static ManualResetEvent manualEvent;
public static void Main()
{
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つのスレッドを交互に実行する例を示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| |
Public Shared manualEvent1 As ManualResetEvent
Public Shared manualEvent2 As ManualResetEvent
Public Shared Sub Main()
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.WaitOne()
manualEvent1.Reset()
Console.Write(Thread.CurrentThread.Name)
manualEvent2.Set()
Next i
End Sub
Public Shared Sub MyMethod2()
Dim i As Integer
For i = 0 To 99
manualEvent2.WaitOne()
manualEvent2.Reset()
Console.Write(Thread.CurrentThread.Name)
manualEvent1.Set()
Next i
End Sub
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| |
public static ManualResetEvent manualEvent1;
public static ManualResetEvent manualEvent2;
public static void Main()
{
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.Set();
Console.ReadLine();
}
public static void MyMethod1()
{
for (int i = 0; i < 100; i++)
{
manualEvent1.WaitOne();
manualEvent1.Reset();
Console.Write(Thread.CurrentThread.Name);
manualEvent2.Set();
}
}
public static void MyMethod2()
{
for (int i = 0; i < 100; i++)
{
manualEvent2.WaitOne();
manualEvent2.Reset();
Console.Write(Thread.CurrentThread.Name);
manualEvent1.Set();
}
}
|
//出力結果例:
//1212121212121212121212121212121212121212121212121212121212121212121212
//1212121212121212121212121212121212121212121212121212121212121212121212
//121212121212121212121212121212121212121212121212121212121212
上記のコードではManualResetEvent.WaitOneメソッドの後すぐにResetメソッドを呼び出して非シグナル状態に戻していますが、ManualResetEventの代わりにAutoResetEventを使うことにより、この手間が省けます。AutoResetEventでは、シグナルを待機中のスレッドがすべて解放されると、自動的に非シグナル状態にリセットされるのです。(待機中のスレッドがない場合は、無限にシグナル状態のままとなります。)
以下に上記のコードをAutoResetEventを使って書き換えた例を示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| |
Public Shared autoEvent1 As AutoResetEvent
Public Shared autoEvent2 As AutoResetEvent
Public Shared Sub Main()
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.Set()
Console.ReadLine()
End Sub
Public Shared Sub MyMethod1()
Dim i As Integer
For i = 0 To 99
autoEvent1.WaitOne()
Console.Write(Thread.CurrentThread.Name)
autoEvent2.Set()
Next i
End Sub
Public Shared Sub MyMethod2()
Dim i As Integer
For i = 0 To 99
autoEvent2.WaitOne()
Console.Write(Thread.CurrentThread.Name)
autoEvent1.Set()
Next i
End Sub
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| |
public static AutoResetEvent autoEvent1;
public static AutoResetEvent autoEvent2;
public static void Main()
{
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.Set();
Console.ReadLine();
}
public static void MyMethod1()
{
for (int i = 0; i < 100; i++)
{
autoEvent1.WaitOne();
Console.Write(Thread.CurrentThread.Name);
autoEvent2.Set();
}
}
public static void MyMethod2()
{
for (int i = 0; i < 100; i++)
{
autoEvent2.WaitOne();
Console.Write(Thread.CurrentThread.Name);
autoEvent1.Set();
}
}
|
複数の待機ハンドルのシグナル状態により待機が解除されるようにするには、WaitOneではなく、WaitAllまたはWaitAnyメソッドを使用します。WaitAllメソッドでは指定されたすべての待機ハンドルがシグナル状態になるまで待機し、WaitAnyメソッドでは指定されたいずれかの待機ハンドルがシグナル状態になるまで待機します。
Mutexクラス
Mutexは同時に1つのスレッドでしか所有できない同期オブジェクトです。
Mutexを使用して同期を行う基本的な方法は、次のようなものです。まずミューテックスの所有権を持たないスレッドがWaitOneメソッドを呼び出して所有権を要求します。この時ミューテックスの所有権を持つスレッドがなければ、このスレッドが所有権を取得しますが、別のスレッドが所有権を取得していれば、WaitOneメソッドを呼び出したスレッドは待機状態になります。ミューテックスを所有しているスレッドがReleaseMutexメソッドを呼び出すか、あるいは正常終了してミューテックスの所有を解放すると、待機中の次のスレッドが所有権を取得します。
なお、ミューテックスを所有しているスレッドがさらにWaitOneメソッドを繰り返した場合、ミューテックスの所有を解放するためには、ReleaseMutexメソッドを同じだけ呼び出す必要があります。
Mutexを使用した簡単な例を次に示します。MyMethod1メソッドのMutex.WaitOneとMutex.ReleaseMutexで囲まれた所が1つのスレッドのみが入れる同期された部分となります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| |
Public Shared mut As Mutex
Public Shared Sub Main()
mut = New Mutex
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)
mut.ReleaseMutex()
Next i
End Sub
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| |
public static Mutex mut;
public static void Main()
{
mut = new Mutex();
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);
mut.ReleaseMutex();
}
}
|
Mutexの大きな特徴としてはさらに、スレッド間の同期だけではなく、プロセス間の同期にも使用できる点があげられます。アプリケーションの二重起動を防止するためにMutexを使用する方法は、「.NET Tips - 二重起動を禁止する」で紹介しています。
参考:
コメント †