高度な同期化技法
更新 : 2007 年 11 月
マルチスレッド アプリケーションは、複数のスレッドの同期をとるために待機ハンドルと監視オブジェクトを使用することがよくあります。以下のセクションでは、.NET Framework のクラスを使用してスレッドの同期をとる方法について説明します。ここで取り上げるのは、AutoResetEvent、Interlocked、ManualResetEvent、Monitor、Mutex、ReaderWriterLock、Timer、および WaitHandle の各クラスです。
待機ハンドル
待機ハンドルは、あるスレッドのステータスを別のスレッドに通知するオブジェクトです。スレッドは、待機ハンドルを使用して、リソースの排他アクセスが必要であることを他のスレッドに通知できます。通知を受けたスレッドは、待機ハンドルが使用されなくなるまで、このリソースの使用を待つ必要があります。待機ハンドルの状態には、"シグナル状態" と "非シグナル状態" の 2 種類があります。どのスレッドにも所有されていない待機ハンドルは、"シグナル状態" です。スレッドによって所有されている待機ハンドルは、"非シグナル状態" です。
スレッドは、WaitOne、WaitAny、WaitAll などのいずれかの待機メソッドを呼び出して、待機ハンドルの所有権を要求します。待機メソッドは、個別のスレッドに対する Join メソッドと同様に、呼び出しをブロックするメソッドです。
待機ハンドルを所有しているスレッドがない場合、待機メソッドを呼び出すとすぐに True が戻ります。待機ハンドルの状態は非シグナル状態に変わり、待機ハンドルを所有するスレッドは実行を継続します。
スレッドが待機ハンドルの待機メソッドを呼び出すときに待機ハンドルが別のスレッドに所有されている場合、呼び出し元スレッドは一定時間待つか (タイムアウトを指定する場合)、待機ハンドルを所有しているスレッドが待機ハンドルを解放するまで待ちます (タイムアウトを指定しない場合)。タイムアウトを指定し、タイムアウトが経過する前に待機ハンドルが解放された場合、True が戻ります。それ以外の場合は、False が戻り、呼び出し元スレッドは実行を継続します。
待機ハンドルを所有するスレッドは、終了時または待機ハンドルが不要になったときに Set メソッドを呼び出します。他のスレッドは、Reset メソッドを呼び出すか、または WaitOne、WaitAny、または WaitAll を呼び出し、スレッドが Set を呼び出すのをきちんと待機することによって、待機ハンドルの状態を非シグナル状態にリセットできます。AutoResetEvent ハンドルは、単一の待機スレッドが解放された後で、システムによって自動的に非シグナル状態にリセットされます。待機中のスレッドがない場合、イベント オブジェクトはシグナル状態のままです。
Visual Basic で一般的に使用される待機ハンドルには、ミューテックス オブジェクト、ManualResetEvent、および AutoResetEvent の 3 種類があります。ManualResetEvent と AutoResetEvent は、同期イベントと呼ばれます。
ミューテックス オブジェクト
ミューテックス オブジェクトは、同時に 1 つのスレッドでしか所有できない同期オブジェクトです。"ミューテックス" (mutex) という名前は、ミューテックス オブジェクトの所有が相互に排他的 (mutually exclusive) であることに由来します。スレッドは、リソースを排他的にアクセスする必要があるときに、ミューテックス オブジェクトの所有を要求します。ミューテックス オブジェクトを同時に所有できるスレッドは 1 つだけであるため、他のスレッドはミューテックス オブジェクトの所有を待ってからリソースを使用する必要があります。
WaitOne メソッドを呼び出すと、呼び出し元スレッドはミューテックス オブジェクトの所有を待ちます。ミューテックス オブジェクトを所有しているスレッドが通常どおりに終了すると、ミューテックス オブジェクトはシグナル状態になり、待機中の次のスレッドが所有します。
同期イベント
同期イベントは、なんらかの処理が行われたこと、またはリソースが使用できることを他のスレッドに通知します。"イベント" という言葉が使われてはいますが、同期イベントは、Visual Basic の他のイベントとは異なり、その実体は待機ハンドルです。他の待機ハンドルと同様に、同期イベントの状態には、"シグナル状態" と "非シグナル状態" の 2 種類があります。
同期イベントの待機メソッドのいずれかを呼び出すスレッドは、別のイベントが Set メソッドを呼び出してイベントを通知するまで待つ必要があります。同期イベントには、ManualResetEvent と AutoResetEvent の 2 つのクラスがあります。
スレッドは、Set メソッドを使用して、ManualResetEvent インスタンスをシグナル状態に設定します。また、スレッドは、Reset メソッドを使用して、または待機中の WaitOne 呼び出しに制御が戻ったときに、ManualResetEvent インスタンスを非シグナル状態に設定します。
AutoResetEvent クラスのインスタンスも Set によってシグナル状態に設定できますが、イベントがシグナル状態になったことを示す通知を待機中のスレッドが受け取った時点で、すぐに自動的に非シグナル状態に戻ります。
監視オブジェクトと SyncLock
監視オブジェクトは、コードのブロックが他のスレッドで実行中のコードによって中断されずに実行されるように使用します。つまり、他のスレッドのコードは、同期されたコード ブロックのコードが終了するまで実行できません。
たとえば、データの読み出しと結果の表示を非同期で繰り返し行うプログラムがあるとします。プリエンプティブ マルチタスクを使用するオペレーティング システムでは、他のスレッドの実行に時間を割り当てるために、実行中のスレッドがオペレーティング システムによって中断されることがあります。同期を行わない場合、データの表示中にデータを表すオブジェクトが別のスレッドによって更新されると、データの表示が部分的に更新されることがあります。監視オブジェクトにより、コードのセクションの実行が中断されないことが保証されます。Visual Basic では、SyncLock ステートメントと End SyncLock ステートメントを使用して、監視オブジェクトを簡単に利用できます。Visual C# では、Lock キーワードを同様に使用します。
メモ : |
---|
オブジェクトへのアクセスは、アクセスするコードが同じオブジェクト インスタンスの SyncLock ブロック内にある場合にだけロックアウトされます。 |
SyncLock ステートメントの詳細については、「SyncLock ステートメント」を参照してください。
Interlocked クラス
Interlocked クラスのメソッドを使用すると、複数のスレッドが同じ値を同時に更新または比較しようとするときに発生する問題を回避できます。このクラスのメソッドによって、どのスレッドの値も安全にインクリメント、デクリメント、交換、比較することができます。別々のスレッドで実行中のプロシージャによって共有されている変数をインクリメントするために Increment メソッドを使用する方法を次の例に示します。
Sub ThreadA(ByRef IntA As Integer)
System.Threading.Interlocked.Increment(IntA)
End Sub
Sub ThreadB(ByRef IntA As Integer)
System.Threading.Interlocked.Increment(IntA)
End Sub
ReaderWriter ロック
場合によっては、データを書き込むときだけリソースをロックし、データが更新されないときには複数のクライアントにデータの同時読み取りを許可する場合があります。ReaderWriterLock クラスを使用すると、スレッドがリソースを変更する間はリソースに排他アクセスを適用し、リソースを読み取るときには排他的でないアクセスを許可できます。ReaderWriter ロックは、データの更新が必要ないときでも、他のスレッドを待たせる方法として、排他ロックに代わる有効な手段です。
デッドロック
スレッドの同期は、マルチスレッド アプリケーションにとって非常に大切ですが、deadlock を生じさせてしまう危険性が常にあります。つまり、複数のスレッドが互いに待機しあって、アプリケーションが中断してしまう状態です。デッドロックをたとえて言えば、四方向一時停止の交差点で停止した車どうしが、相手の車を先に行かせるよう互いに譲り合い、どちらも動けなくなっている状態と同じです。デッドロックを防ぐことは重要です。その鍵となるのは、綿密なプランです。コーディングを始める前にマルチスレッド アプリケーションを図式化すると、デッドロック状態を予想できることがよくあります。