更新 : 2007 年 11 月
.NET Framework には、スレッドの対話を制御し、競合状態を回避するためのさまざまな同期プリミティブが用意されています。同期プリミティブは、ロック、シグナル、インタロックされた操作の 3 つのカテゴリに大きく分けることができます。
このカテゴリは、系統立てられ明確に定義されているわけではありません。複数のカテゴリの特性を備えた同期機構もあります。一度に 1 つのスレッドを解放するイベントは、機能的にはロックと同様です。ロックの解放はシグナルと考えることができます。また、インタロックされた操作を使用してロックを構築できます。ただし、これらのカテゴリはやはり有用です。
スレッド同期は協調的であることに留意することが重要です。スレッドが 1 つでも同期機構を迂回し、保護されたリソースに直接アクセスするようなことがあれば、その同期機構の効果は失われます。
ロック
ロックは、一度に 1 つのスレッド、または指定した数のスレッドがリソースを制御できるようにします。ロックの使用時に排他ロックを要求するスレッドは、ロックが使用できるようになるまでブロックします。
排他ロック
ロックの最も簡単な形式は、コード ブロックへのアクセスを制御する C# の lock ステートメント (Visual Basic では SyncLock) です。多くの場合、このようなブロックはクリティカル セクションと呼ばれます。lock ステートメントは、Monitor クラスの Enter メソッドと Exit メソッドを使用して実装され、tryccatchcfinally を使用してロックを確実に解放します。
通常、範囲を単一のメソッドに限定し、lock ステートメントを使用してコードの小さなブロックを保護することは、Monitor クラスを使用する際の最適な方法です。Monitor クラスは強力ですが、ロックとデッドロックを孤立させる傾向があります。
クラスの監視
Monitor クラスには、lock ステートメントと組み合わせて使用できる次のような追加機能が用意されています。
TryEnter メソッドは、リソースを待機する間ブロックされているスレッドが、指定された間隔が経過した後に待機を中止できるようにします。このメソッドは、成功か失敗かを示すブール値を返します。この値を使用して、デッドロックが発生する可能性を検出したり回避したりできます。
Wait メソッドは、クリティカル セクションのスレッドによって呼び出されます。このメソッドは、リソースが再び使用できるようになるまでリソースの制御を中止し、ブロックします。
Pulse メソッドと PulseAll メソッドは、ロックを解放しようとしているスレッド、または Wait を呼び出そうとしているスレッドが、1 つ以上のスレッドを実行待ちキューに配置できるようにします。これにより、これらのスレッドはロックを取得できるようになります。
Wait メソッド オーバーロードのタイムアウトにより、待機スレッドは実行待ちキューにエスケープできます。
ロックに使用するオブジェクトが MarshalByRefObject から派生する場合、Monitor クラスは複数のアプリケーション ドメインでロックを提供できます。
Monitor にはスレッド アフィニティがあります。つまり、モニタに入ったスレッドは、Exit または Wait を呼び出して終了する必要があります。
Monitor クラスはインスタンス化できません。このクラスのメソッドは static (Visual Basic では Shared) であり、インスタンス化可能なロック オブジェクトで機能します。
概念については、「Monitor」を参照してください。
Mutex クラス
スレッドは、WaitOne メソッドのオーバーロードを呼び出して Mutex を要求します。スレッドが待機を中止できるように、タイムアウトを持つオーバーロードが用意されています。Monitor クラスとは異なり、ミューテックスはローカルにもグローバルにもできます。グローバル ミューテックスは、名前付きミューテックスとも呼ばれますが、オペレーティング システム全体にわたって参照でき、複数のアプリケーション ドメインまたはプロセスでスレッドを同期するために使用できます。ローカル ミューテックスは MarshalByRefObject から派生し、アプリケーション ドメインの境界を越えて使用できます。
また、Mutex は WaitHandle から派生します。つまり、WaitAll、WaitAny、SignalAndWait の各メソッドなど、WaitHandle で提供されるシグナル機構で使用できます。
Monitor と同様に、Mutex にもスレッド アフィニティがあります。Monitor とは異なり、Mutex はインスタンス化可能なオブジェクトです。
概念については、「ミューテックス」を参照してください。
その他のロック
ロックは排他である必要はありません。多くの場合、リソースに同時アクセスするスレッドの数を制限するうえで役立ちます。セマフォとリーダー ライタ ロックは、この種のプールされたリソース アクセスを制御するようデザインされています。
ReaderWriterLock クラス
ReaderWriterLockSlim クラスは、データを変更するスレッド (ライタ) がリソースに排他アクセスする必要がある状況に対処します。ライタがアクティブでないときには、任意の数のリーダーが (たとえば EnterReadLock メソッドを呼び出して) リソースにアクセスできます。スレッドが排他アクセスを要求すると (たとえば EnterWriteLock メソッドが呼び出されると)、既存のすべてのリーダーがロックを終了し、ライタがロックを開始して終了するまで、以降のリーダーの要求はブロックされます。
ReaderWriterLockSlim にはスレッド アフィニティがあります。
概念については、「読み取り/書き込みロック」を参照してください。
Semaphore クラス
Semaphore クラスは、指定した数のスレッドがリソースにアクセスできるようにします。スレッドがセマフォを解放するまで、リソースを要求する他のスレッドはブロックされます。
Mutex クラスと同様に、Semaphore は WaitHandle から派生します。また、Mutex と同様に、Semaphore はローカルにもグローバルにもできます。このクラスは、アプリケーション ドメインの境界を越えて使用できます。
Monitor、Mutex、および ReaderWriterLock とは異なり、Semaphore にはスレッド アフィニティはありません。つまり、あるスレッドがセマフォを取得し、別のスレッドがそれを解放するシナリオで使用できます。
概念については、「セマフォ」を参照してください。
シグナル
別のスレッドからのシグナルを待機する最も簡単な方法は、他のスレッドが完了するまでブロックする Join メソッドを呼び出すことです。Join には、指定した間隔が経過した後、ブロックされたスレッドが待機状態を中止できる 2 つのオーバーロードがあります。
待機ハンドルには、待機機能とシグナル機能の豊富なセットが用意されています。
待機ハンドル
待機ハンドルは、MarshalByRefObject から派生した WaitHandle クラスから派生します。したがって、待機ハンドルを使用すると、アプリケーション ドメインの境界を越えてスレッドのアクティビティを同期できます。
スレッドは、WaitOne インスタンス メソッド、または WaitAll、WaitAny、または SignalAndWait のいずれかの静的メソッドを呼び出して待機ハンドル上でブロックします。スレッドが解放される方法は、呼び出されたメソッドや待機ハンドルの種類によって異なります。
概念については、「待機ハンドル」を参照してください。
イベント待機ハンドル
イベント待機ハンドルには、EventWaitHandle クラスとその派生クラス、AutoResetEvent、および ManualResetEvent が含まれます。Set メソッドを呼び出すか、SignalAndWait メソッドを使用することにより、イベント待機ハンドルがシグナル状態になると、スレッドはそのイベント待機ハンドルから解放されます。
イベント待機ハンドルは、シグナル状態になるたびに 1 つのスレッドだけが通過できる回転ドアのように、自身を自動的にリセットするか、またはシグナル状態になるまで閉じられ、だれかが閉じるまで開いているゲートのように、手動でリセットする必要があります。名前が示すように、AutoResetEvent は前者を、ManualResetEvent は後者を表します。
EventWaitHandle は、いずれかの種類のイベントを表すことができ、ローカルにもグローバルにもできます。派生クラス AutoResetEvent と ManualResetEvent は、常にローカルです。
イベント待機ハンドルには、スレッド アフィニティはありません。どのスレッドでもイベント待機ハンドルをシグナル状態にできます。
概念については、「EventWaitHandle、AutoResetEvent、および ManualResetEvent」を参照してください。
Mutex クラスと Semaphore クラス
Mutex クラスと Semaphore クラスは WaitHandle から派生するため、WaitHandle の静的メソッドで使用できます。たとえば、WaitAll メソッドを使用すると、EventWaitHandle がシグナル状態になる、Mutex が解放される、Semaphore が解放される、という 3 つの条件がすべて当てはまるまでスレッドは待機できます。同様に、WaitAny メソッドを使用すると、スレッドはこれらの条件のいずれか 1 つが当てはまるまで待機できます。
Mutex または Semaphore では、シグナル状態は解放された状態を意味します。いずれかの型が SignalAndWait メソッドの最初の引数として使用された場合、その型は解放されます。スレッド アフィニティのある Mutex の場合、呼び出し元のスレッドがミューテックスを所有していなければ、例外がスローされます。前述のとおり、セマフォにはスレッド アフィニティはありません。
インタロックされた操作
インタロックされた操作は、Interlocked クラスの静的メソッドによってメモリ位置で実行される単純な分割不可能操作です。分割不可能な操作には、加算、インクリメントとデクリメント、交換、比較に依存する条件付き交換、32 ビット プラットフォームでの 64 ビット値の読み取り操作などがあります。
![]() |
---|
分割不能性の保証は個々の操作に限定されます。複数の操作を 1 つの単位として実行する必要がある場合、粒度の粗い同期機構を使用する必要があります。 |
これらの操作はいずれもロックまたはシグナルではありませんが、ロックおよびシグナルの構築に使用できます。インタロックされた操作は Windows オペレーティング システムにネイティブであるため、非常に高速です。
インタロックされた操作を揮発性メモリの保証で使用すると、強力な非ブロッキング同時実行を示すアプリケーションを作成できますが、高度な低水準プログラミングを必要とするため、ほとんどの目的では単純なロックが適切な選択といえます。
概念については、「インタロックされた操作」を参照してください。