System.Threading.Monitor クラス
この記事では、この API のリファレンス ドキュメントへの補足的な解説を提供します。
このMonitorクラスを使用すると、特定のオブジェクトに対するロックを取得して解放することで、コード領域へのアクセスを同期できます。そのためには、,Monitor.TryEnter、メソッドをMonitor.Exit呼び出Monitor.Enterします。 オブジェクト ロックを使用すると、コード ブロック (一般にクリティカル セクションと呼ばれます) へのアクセスを制限できます。 スレッドはオブジェクトのロックを所有していますが、他のスレッドはそのロックを取得できません。 クラスを Monitor 使用して、ロック所有者によって実行されているアプリケーション コードのセクションに他のスレッドがアクセスできないようにすることもできます。ただし、他のスレッドが別のロックされたオブジェクトを使用してコードを実行している場合を除きます。 Monitor クラスにはスレッド アフィニティがあるため、ロックを取得したスレッドは、Monitor.Exit メソッドを呼び出してロックを解放する必要があります。
概要
Monitor には、次の機能があります。
- これは、必要に応じてオブジェクトに関連付けられます。
- バインドされていないので、任意のコンテキストから直接呼び出すことができます。
- クラスのインスタンスを Monitor 作成できません。クラスの Monitor メソッドはすべて静的です。 各メソッドには、クリティカル セクションへのアクセスを制御する同期オブジェクトが渡されます。
Note
クラスを Monitor 使用して、文字列以外のオブジェクト (つまり、値型ではなく、参照型) Stringをロックします。 詳細については、この記事の後半の「メソッドのオーバーロード Enter 」および 「ロック オブジェクト 」セクションを参照してください。
次の表では、同期されたオブジェクトにアクセスするスレッドが実行できるアクションについて説明します。
アクション | 説明 |
---|---|
Enter, TryEnter | オブジェクトのロックを取得します。 このアクションは、クリティカル セクションの先頭にもマークを付けます。 別のロックされたオブジェクトを使用してクリティカル セクションの命令を実行している場合を除き、他のスレッドはクリティカル セクションに入ることができます。 |
Wait | 他のスレッドがオブジェクトをロックおよびアクセスできるようにするために、オブジェクトのロックを解放します。 呼び出し元のスレッドは、別のスレッドがオブジェクトにアクセスするまで待機します。 パルス信号は、オブジェクトの状態への変更について待機中のスレッドに通知するために使用されます。 |
Pulse (シグナル) PulseAll | 1 つ以上の待機中のスレッドにシグナルを送信します。 このシグナルは、ロックされたオブジェクトの状態が変更され、ロックの所有者がロックを解除する準備ができていることを待機中のスレッドに通知します。 待機中のスレッドは、最終的にオブジェクトのロックを受け取ることができるように、オブジェクトの準備完了キューに配置されます。 スレッドがロックされると、オブジェクトの新しい状態をチェックして、必要な状態に達したかどうかを確認できます。 |
Exit | オブジェクトのロックを解除します。 このアクションは、ロックされたオブジェクトによって保護されているクリティカル セクションの末尾もマークします。 |
メソッドには TryEnter 2 つのオーバーロードEnterセットがあります。 1 セットのオーバーロードには、ref
ロックの取得時に例外がスローされた場合でも、ロックが取得された場合にアトミックにtrue
設定される (C# の) パラメーターまたは ByRef
(Visual Basic の場合) Boolean パラメーターがあります。 ロックが保護されているリソースが一貫した状態ではない可能性がある場合でも、すべてのケースでロックを解放することが重要な場合は、これらのオーバーロードを使用します。
ロック オブジェクト
Monitor クラスは、クリティカル セクションへのアクセスを制御するオブジェクトを操作する (Shared
Visual Basic の場合) メソッドで構成されますstatic
。 同期されたオブジェクトごとに、次の情報がメインされます。
- 現在ロックを保持しているスレッドへの参照。
- ロックを取得する準備ができているスレッドを含む準備完了キューへの参照。
- 待機中のキューへの参照。ロックされたオブジェクトの状態の変化の通知を待機しているスレッドが含まれます。
Monitor は値型ではなく、オブジェクト (つまり、参照型) をロックします。 値型を Enter と Exit に渡すことができますが、値型は呼び出しごとに個別にボックス化されます。 呼び出しごとに個別のオブジェクトが作成されるので、Enter は決してコードをブロックすることはなく、保護していると想定しているコードは実際には同期されません。 さらに、Exit に渡されたオブジェクトは Enter に渡されたオブジェクトとは異なるため、Monitor は「オブジェクトの同期メソッドが、コードの非同期ブロックから呼び出されました。」というメッセージとともに SynchronizationLockException 例外をスローします。
この問題を説明する例を次に示します。 10 個のタスクが起動され、それぞれが 250 ミリ秒間スリープ状態になります。 次に、各タスクはカウンター変数である nTasks
を更新します。これは実際に起動、実行されるタスクの数をカウントするためのものです。 nTasks
は複数のタスクで同時に更新可能なグローバル変数なので、複数のタスクによる同時変更を防止するためにモニターを使用します。 しかし、例に示す出力のように、各タスクは SynchronizationLockException 例外をスローします。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example1
{
public static void Main()
{
int nTasks = 0;
List<Task> tasks = new List<Task>();
try
{
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run(() =>
{ // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(nTasks);
try
{
nTasks += 1;
}
finally
{
Monitor.Exit(nTasks);
}
}));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("{0} tasks started and executed.", nTasks);
}
catch (AggregateException e)
{
String msg = String.Empty;
foreach (var ie in e.InnerExceptions)
{
Console.WriteLine("{0}", ie.GetType().Name);
if (!msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
//
// Exception Message(s):
// Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example3
Public Sub Main()
Dim nTasks As Integer = 0
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(nTasks)
Try
nTasks += 1
Finally
Monitor.Exit(nTasks)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
'
' Exception Message(s):
' Object synchronization method was called from an unsynchronized block of code.
各タスクの Monitor.Enter メソッドに対する呼び出しの前に nTasks
変数がボックス化されるため、各タスクは SynchronizationLockException 例外をスローします。 つまり、各メソッドの呼び出しは他のメソッドから独立している個別の変数に渡されます。 nTasks
は Monitor.Exit メソッドへの呼び出しで再びボックス化されます。 こうして 10 個の新しいボックス化された変数が作成されます。これらは互いに独立したものであり、nTasks
からも Monitor.Enter メソッドへの呼び出しで作成された 10 個のボックス化された変数からも独立しています。 それで、以前ロックされていなかった新規に作成された変数のロックを解放しようとしているため、例外がスローされます。
次の例に示すように、Enter と Exit の呼び出しの前に値型の変数をボックス化したり、ボックス化された同じオブジェクトを両方のメソッドに渡したりできますが、これを行う利点はありません。 ボックス化解除された変数への変更は、ボックス化されたコピーには反映されません。またボックス化されたコピーの値を変更する方法はありません。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
int nTasks = 0;
object o = nTasks;
List<Task> tasks = new List<Task>();
try {
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(o);
try {
nTasks++;
}
finally {
Monitor.Exit(o);
}
} ));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("{0} tasks started and executed.", nTasks);
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine("{0}", ie.GetType().Name);
if (! msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// 10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example2
Public Sub Main()
Dim nTasks As Integer = 0
Dim o As Object = nTasks
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(o)
Try
nTasks += 1
Finally
Monitor.Exit(o)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' 10 tasks started and executed.
同期するオブジェクトを選択するときは、プライベート オブジェクトまたは内部オブジェクトでのみロックする必要があります。 外部オブジェクトをロックするとデッドロックが発生する可能性があります。これは、関連のないコードが、異なる目的でロックする同じオブジェクトを選択する可能性があるためです。
ロックに使用されるオブジェクトの派生MarshalByRefObject元の場合はメイン複数のアプリケーションのオブジェクトで同期できることに注意してください。
クリティカル セクション
メソッドをEnterExit使用して、クリティカル セクションの先頭と末尾をマークします。
Note
およびメソッドによってEnter提供される機能は、C# の lock ステートメントと Visual Basic の SyncLock ステートメントで提供される機能と同じですが、言語コンストラクトがメソッドのオーバーロードとメソッドを try
Monitor.Exit ... でラップMonitor.Enter(Object, Boolean)する点が異Exitなります。finally
をブロックして、モニターが確実に解放されるようにします。
クリティカル セクションが連続した命令のセットである場合、メソッドによって取得されたロックにより Enter 、ロックされたオブジェクトで囲まれたコードを 1 つのスレッドのみが実行できることを保証します。 この場合は、そのコードをブロックに try
配置し、メソッドの呼び出しを Exit ブロックに finally
配置することをお勧めします。 これにより、例外が発生しても必ずロックが解放されるようになります。 次のコード フラグメントは、このパターンを示しています。
// Define the lock object.
var obj = new Object();
// Define the critical section.
Monitor.Enter(obj);
try
{
// Code to execute one thread at a time.
}
// catch blocks go here.
finally
{
Monitor.Exit(obj);
}
' Define the lock object.
Dim obj As New Object()
' Define the critical section.
Monitor.Enter(obj)
Try
' Code to execute one thread at a time.
' catch blocks go here.
Finally
Monitor.Exit(obj)
End Try
通常、この機能は、クラスの静的メソッドまたはインスタンス メソッドへのアクセスを同期するために使用されます。
クリティカル セクションがメソッド全体にまたがる場合は、メソッドに配置System.Runtime.CompilerServices.MethodImplAttributeし、次のコンストラクターSystem.Runtime.CompilerServices.MethodImplAttributeに値をSynchronized指定することで、ロック機能を実現できます。 この属性を使用する場合、 Enter メソッド呼び出しと Exit メソッド呼び出しは必要ありません。 次のコード フラグメントは、このパターンを示しています。
[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
// Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
' Method implementation.
End Sub
属性によって、メソッドが戻るまで、ロックを保持するために、現在のスレッドに注意してください。ロックがすぐに解放する場合は、使用、Monitorクラスの C#ロックステートメント、または Visual Basic SyncLock属性ではなく、メソッド内でステートメント。
特定のオブジェクトをロックおよび解放する Enter and Exit ステートメントがメンバーまたはクラスの境界または両方を超える場合は可能ですが、この方法はお勧めしません。
Pulse、PulseAll、Wait
スレッドがロックを所有し、ロックが保護する重要なセクションに入ると、そのスレッドは 、、Monitor.PulseおよびMonitor.PulseAllメソッドをMonitor.Wait呼び出すことができます。
ロックを保持するスレッドが呼び出 Waitされると、ロックが解放され、スレッドが同期オブジェクトの待機キューに追加されます。 準備完了キュー内の最初のスレッド (存在する場合) はロックを取得し、クリティカル セクションに入ります。 呼び出されたWaitスレッドは、ロックを保持しているスレッドによってメソッドがMonitor.PulseAll呼び出されたときにMonitor.Pulse、待機キューから準備完了キューに移動されます (移動するには、スレッドが待機キューの先頭にある必要があります)。 呼 Wait び出し元のスレッドがロックを再取得すると、メソッドは返します。
ロックを保持するスレッドが呼び出 Pulseされると、待機中のキューの先頭にあるスレッドが準備完了キューに移動されます。 メソッドを PulseAll 呼び出すと、すべてのスレッドが待機キューから準備完了キューに移動されます。
モニターと待機ハンドル
クラスとWaitHandleオブジェクトのMonitor使用の区別に注意することが重要です。
- この Monitor クラスは純粋に管理され、完全に移植可能であり、オペレーティング システムのリソース要件の観点からより効率的な場合があります。
- WaitHandle オブジェクトはオペレーティング システムの待機可能オブジェクトを表しており、マネージドとアンマネージド コード間で同期するのに便利です。また一度に多くのオブジェクトを待機できる機能などの高度なオペレーティング システム機能を公開します。
例
次の例では、クラスを Monitor 使用して、クラスによって表される乱数ジェネレーターの 1 つのインスタンスへのアクセスを Random 同期します。 この例では、10 個のタスクを作成します。各タスクはスレッド プール スレッドで非同期的に実行されます。 各タスクは、10,000 個の乱数を生成し、その平均値を計算し、生成された乱数の総数とその合計をメイン含む 2 つのプロシージャ レベルの変数を更新します。 すべてのタスクが実行された後、これら 2 つの値を使用して全体の平均が計算されます。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example2
{
public static void Main()
{
List<Task> tasks = new List<Task>();
Random rnd = new Random();
long total = 0;
int n = 0;
for (int taskCtr = 0; taskCtr < 10; taskCtr++)
tasks.Add(Task.Run(() =>
{
int[] values = new int[10000];
int taskTotal = 0;
int taskN = 0;
int ctr = 0;
Monitor.Enter(rnd);
// Generate 10,000 random integers
for (ctr = 0; ctr < 10000; ctr++)
values[ctr] = rnd.Next(0, 1001);
Monitor.Exit(rnd);
taskN = ctr;
foreach (var value in values)
taskTotal += value;
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, (taskTotal * 1.0) / taskN,
taskN);
Interlocked.Add(ref n, taskN);
Interlocked.Add(ref total, taskTotal);
}));
try
{
Task.WaitAll(tasks.ToArray());
Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n);
}
catch (AggregateException e)
{
foreach (var ie in e.InnerExceptions)
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);
}
}
}
// The example displays output like the following:
// Mean for task 1: 499.04 (N=10,000)
// Mean for task 2: 500.42 (N=10,000)
// Mean for task 3: 499.65 (N=10,000)
// Mean for task 8: 502.59 (N=10,000)
// Mean for task 5: 502.75 (N=10,000)
// Mean for task 4: 494.88 (N=10,000)
// Mean for task 7: 499.22 (N=10,000)
// Mean for task 10: 496.45 (N=10,000)
// Mean for task 6: 499.75 (N=10,000)
// Mean for task 9: 502.79 (N=10,000)
//
// Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example4
Public Sub Main()
Dim tasks As New List(Of Task)()
Dim rnd As New Random()
Dim total As Long = 0
Dim n As Integer = 0
For taskCtr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
Dim values(9999) As Integer
Dim taskTotal As Integer = 0
Dim taskN As Integer = 0
Dim ctr As Integer = 0
Monitor.Enter(rnd)
' Generate 10,000 random integers.
For ctr = 0 To 9999
values(ctr) = rnd.Next(0, 1001)
Next
Monitor.Exit(rnd)
taskN = ctr
For Each value In values
taskTotal += value
Next
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, taskTotal / taskN,
taskN)
Interlocked.Add(n, taskN)
Interlocked.Add(total, taskTotal)
End Sub))
Next
Try
Task.WaitAll(tasks.ToArray())
Console.WriteLine()
Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n)
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
End Try
End Sub
End Module
' The example displays output like the following:
' Mean for task 1: 499.04 (N=10,000)
' Mean for task 2: 500.42 (N=10,000)
' Mean for task 3: 499.65 (N=10,000)
' Mean for task 8: 502.59 (N=10,000)
' Mean for task 5: 502.75 (N=10,000)
' Mean for task 4: 494.88 (N=10,000)
' Mean for task 7: 499.22 (N=10,000)
' Mean for task 10: 496.45 (N=10,000)
' Mean for task 6: 499.75 (N=10,000)
' Mean for task 9: 502.79 (N=10,000)
'
' Mean for all tasks: 499.75 (N=100,000)
スレッド プール スレッドで実行されている任意のタスクからアクセスできるため、変数 total
へのアクセスも n
同期する必要があります。 このメソッドは Interlocked.Add 、この目的で使用されます。
次のMonitor例では、クラス (またはSyncLock
言語コンストラクトでlock
実装)、クラス、およびクラスをInterlocked組み合わせて使用する方法をAutoResetEvent示します。 2 つの internal
クラス (C# の場合) または Friend
クラス (Visual Basic の場合)、SyncResource
と UnSyncResource
を定義します。これらはそれぞれ、リソースへの同期アクセスと非同期アクセスを提供します。 同期アクセスと非同期アクセスの違い (各メソッド呼び出しが迅速に完了する場合に違いが生じる可能性がある) を示すために、次の例では、メソッドにランダムな遅延を含めてあります。Thread.ManagedThreadId プロパティが偶数であるスレッドでは、メソッドが Thread.Sleep を呼び出して、2,000 ミリ秒の遅延を生じさせます。 SyncResource
クラスはパブリックではなく、同期されたリソースでロックを取得するクライアント コードは存在しないので、内部クラス自体がロックを取得することに注意してください。 これにより、悪意のあるコードがパブリック オブジェクトでロックを取得するのを防ぐことができます。
using System;
using System.Threading;
internal class SyncResource
{
// Use a monitor to enforce synchronization.
public void Access()
{
lock(this) {
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
}
}
}
internal class UnSyncResource
{
// Do not enforce synchronization.
public void Access()
{
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
}
}
public class App
{
private static int numOps;
private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
private static SyncResource SyncRes = new SyncResource();
private static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main()
{
// Set the number of synchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\n");
// Reset the count for unsynchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
}
static void SyncUpdateResource(Object state)
{
// Call the internal synchronized method.
SyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
static void UnSyncUpdateResource(Object state)
{
// Call the unsynchronized method.
UnSyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
}
// The example displays output like the following:
// Starting synchronized resource access on thread #6
// Stopping synchronized resource access on thread #6
// Starting synchronized resource access on thread #7
// Stopping synchronized resource access on thread #7
// Starting synchronized resource access on thread #3
// Stopping synchronized resource access on thread #3
// Starting synchronized resource access on thread #4
// Stopping synchronized resource access on thread #4
// Starting synchronized resource access on thread #5
// Stopping synchronized resource access on thread #5
//
// All synchronized operations have completed.
//
// Starting unsynchronized resource access on Thread #7
// Starting unsynchronized resource access on Thread #9
// Starting unsynchronized resource access on Thread #10
// Starting unsynchronized resource access on Thread #6
// Starting unsynchronized resource access on Thread #3
// Stopping unsynchronized resource access on thread #7
// Stopping unsynchronized resource access on thread #9
// Stopping unsynchronized resource access on thread #3
// Stopping unsynchronized resource access on thread #10
// Stopping unsynchronized resource access on thread #6
//
// All unsynchronized thread operations have completed.
Imports System.Threading
Friend Class SyncResource
' Use a monitor to enforce synchronization.
Public Sub Access()
SyncLock Me
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End SyncLock
End Sub
End Class
Friend Class UnSyncResource
' Do not enforce synchronization.
Public Sub Access()
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End Sub
End Class
Public Module App
Private numOps As Integer
Private opsAreDone As New AutoResetEvent(False)
Private SyncRes As New SyncResource()
Private UnSyncRes As New UnSyncResource()
Public Sub Main()
' Set the number of synchronized calls.
numOps = 5
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
Console.WriteLine()
numOps = 5
' Reset the count for unsynchronized calls.
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
End Sub
Sub SyncUpdateResource()
' Call the internal synchronized method.
SyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
Sub UnSyncUpdateResource()
' Call the unsynchronized method.
UnSyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
End Module
' The example displays output like the following:
' Starting synchronized resource access on thread #6
' Stopping synchronized resource access on thread #6
' Starting synchronized resource access on thread #7
' Stopping synchronized resource access on thread #7
' Starting synchronized resource access on thread #3
' Stopping synchronized resource access on thread #3
' Starting synchronized resource access on thread #4
' Stopping synchronized resource access on thread #4
' Starting synchronized resource access on thread #5
' Stopping synchronized resource access on thread #5
'
' All synchronized operations have completed.
'
' Starting unsynchronized resource access on Thread #7
' Starting unsynchronized resource access on Thread #9
' Starting unsynchronized resource access on Thread #10
' Starting unsynchronized resource access on Thread #6
' Starting unsynchronized resource access on Thread #3
' Stopping unsynchronized resource access on thread #7
' Stopping unsynchronized resource access on thread #9
' Stopping unsynchronized resource access on thread #3
' Stopping unsynchronized resource access on thread #10
' Stopping unsynchronized resource access on thread #6
'
' All unsynchronized thread operations have completed.
例では、リソースにアクセスしようとするスレッドの数を定義する変数 numOps
を定義します。 アプリケーション スレッドは、同期アクセスの場合も非同期アクセスの場合もそれぞれ 5 回、ThreadPool.QueueUserWorkItem(WaitCallback) メソッドを呼び出します。 ThreadPool.QueueUserWorkItem(WaitCallback) メソッドにはパラメーターが 1 つしかありません。パラメーターを受け入れず値を返さないデリゲートです。 同期アクセスの場合は SyncUpdateResource
メソッドを呼び出し、非同期アクセスの場合は UnSyncUpdateResource
メソッドを呼び出します。 メソッド呼び出しの各セットの後、アプリケーション スレッドは AutoResetEvent.WaitOne メソッドを呼び出して、インスタンスが通知されるまでAutoResetEventブロックします。
SyncUpdateResource
メソッドを呼び出すたびに、内部 SyncResource.Access
メソッドが呼び出され、Interlocked.Decrement メソッドが呼び出されて、numOps
カウンターがデクリメントされます。 メソッドは Interlocked.Decrement カウンターのデクリメントに使用されます。それ以外の場合は、最初のスレッドのデクリメントされた値が変数に格納される前に、2 番目のスレッドが値にアクセスすることを確信できないためです。 最後に同期されたワーカー スレッドがカウンターを 0 にデクリメントすると、すべての同期スレッドがリソースSyncUpdateResource
へのアクセスを完了したことを示します。このメソッドはメソッドを呼び出しEventWaitHandle.Set、メイン スレッドに実行を続行するよう通知します。
UnSyncUpdateResource
メソッドを呼び出すたびに、内部 UnSyncResource.Access
メソッドが呼び出され、Interlocked.Decrement メソッドが呼び出されて、numOps
カウンターがデクリメントされます。 もう一度、 Interlocked.Decrement メソッドを使用してカウンターをデクリメントし、最初のスレッドのデクリメントされた値が変数に割り当てられる前に、2 番目のスレッドが値にアクセスしないようにします。 最後に同期されていないワーカー スレッドがカウンターを 0 にデクリメントすると、非同期スレッドがリソースUnSyncUpdateResource
にアクセスする必要がないことを示します。このメソッドはメソッドを呼び出しEventWaitHandle.Set、メイン スレッドに実行を続行するように通知します。
例の出力からわかるように、同期アクセスでは、呼び出し元スレッドが保護リソースを終了してからでないと別のスレッドがそれにアクセスできません。つまり各スレッドはその先行処理を待機します。 その一方で、ロックがない UnSyncResource.Access
メソッドは、スレッドが到達する順序で呼び出されます。
.NET