同期プリミティブの概要

.NET Framework には、スレッドの対話を制御し、競合状態を回避するためのさまざまな同期プリミティブが用意されています。 同期プリミティブは、ロック、シグナル、インタロックされた操作の 3 つのカテゴリに大きく分けることができます。

このカテゴリは、系統立てられ明確に定義されているわけではありません。複数のカテゴリの特性を備えた同期機構もあります。一度に 1 つのスレッドを解放するイベントは、機能的にはロックと同様です。ロックの解放はシグナルと考えることができます。また、インタロックされた操作を使用してロックを構築できます。 ただし、これらのカテゴリはやはり有用です。

スレッド同期は協調的であることに留意することが重要です。 スレッドが 1 つでも同期機構を迂回し、保護されたリソースに直接アクセスするようなことがあれば、その同期機構の効果は失われます。

この概要は、次のセクションで構成されています。

  • ロック

  • シグナル

  • 軽量な同期型

  • SpinWait

  • インタロックされた操作

ロック

ロックは、一度に 1 つのスレッド、または指定した数のスレッドがリソースを制御できるようにします。 ロックの使用時に排他ロックを要求するスレッドは、ロックが使用できるようになるまでブロックします。

排他ロック

ロックの最も簡単な形式は、コード ブロックへのアクセスを制御する C# の lock ステートメント (Visual Basic では SyncLock) です。 多くの場合、このようなブロックはクリティカル セクションと呼ばれます。 lock ステートメントは、Monitor クラスの Enter メソッドと Exit メソッドを使用して実装され、try…catch…finally を使用してロックを確実に解放します。

通常、範囲を単一のメソッドに限定し、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 から派生し、アプリケーション ドメインの境界を越えて使用できます。

また、MutexWaitHandle から派生します。つまり、WaitAllWaitAnySignalAndWait の各メソッドなど、WaitHandle で提供されるシグナル機構で使用できます。

Monitor と同様に、Mutex にもスレッド アフィニティがあります。 Monitor とは異なり、Mutex はインスタンス化可能なオブジェクトです。

概念については、「ミューテックス」を参照してください。

SpinLock クラス

.NET Framework Version 4 以降では、Monitor に必要なオーバーヘッドによってパフォーマンスが低下する場合は、SpinLock クラスを使用できます。 SpinLock は、クリティカル セクションがロックされていると、ロックが使用可能になるまで単にスピンし続けます。 ロックの保持時間が非常に短い場合は、ブロッキングよりもスピンの方がパフォーマンスが向上します。 ただし、ロックの保持時間が数十サイクルを超える場合は、SpinLockMonitor と機能は変わらないものの、より多くの CPU サイクルを消費するので、他のスレッドやプロセスのパフォーマンスを低下させることがあります。

その他のロック

ロックは排他である必要はありません。 多くの場合、リソースに同時アクセスするスレッドの数を制限するうえで役立ちます。 セマフォとリーダー ライター ロックは、この種のプールされたリソース アクセスを制御するようデザインされています。

ReaderWriterLock クラス

ReaderWriterLockSlim クラスは、データを変更するスレッド (ライター) がリソースに排他アクセスする必要がある状況に対処します。 ライターがアクティブでないときには、任意の数のリーダーが (たとえば EnterReadLock メソッドを呼び出して) リソースにアクセスできます。 スレッドが排他アクセスを要求すると (たとえば EnterWriteLock メソッドが呼び出されると)、既存のすべてのリーダーがロックを終了し、ライターがロックを開始して終了するまで、以降のリーダーの要求はブロックされます。

ReaderWriterLockSlim にはスレッド アフィニティがあります。

概念については、「読み取り/書き込みロック」を参照してください。

Semaphore クラス

Semaphore クラスは、指定した数のスレッドがリソースにアクセスできるようにします。 スレッドがセマフォを解放するまで、リソースを要求する他のスレッドはブロックされます。

Mutex クラスと同様に、SemaphoreWaitHandle から派生します。 また、Mutex と同様に、Semaphore はローカルにもグローバルにもできます。 このクラスは、アプリケーション ドメインの境界を越えて使用できます。

MonitorMutex、および ReaderWriterLock とは異なり、Semaphore にはスレッド アフィニティはありません。 つまり、あるスレッドがセマフォを取得し、別のスレッドがそれを解放するシナリオで使用できます。

概念については、「Semaphore と SemaphoreSlim」を参照してください。

System.Threading.SemaphoreSlim は、単一プロセスの境界内での同期に使用できる軽量なセマフォです。

ページのトップへ

シグナル

別のスレッドからのシグナルを待機する最も簡単な方法は、他のスレッドが完了するまでブロックする Join メソッドを呼び出すことです。 Join には、指定した間隔が経過した後、ブロックされたスレッドが待機状態を中止できる 2 つのオーバーロードがあります。

待機ハンドルには、待機機能とシグナル機能の豊富なセットが用意されています。

待機ハンドル

待機ハンドルは、MarshalByRefObject から派生した WaitHandle クラスから派生します。 したがって、待機ハンドルを使用すると、アプリケーション ドメインの境界を越えてスレッドのアクティビティを同期できます。

スレッドは、WaitOne インスタンス メソッド、または WaitAllWaitAny、または SignalAndWait のいずれかの静的メソッドを呼び出して待機ハンドル上でブロックします。 スレッドが解放される方法は、呼び出されたメソッドや待機ハンドルの種類によって異なります。

概念については、「待機ハンドル」を参照してください。

イベント待機ハンドル

イベント待機ハンドルには、EventWaitHandle クラスとその派生クラス、AutoResetEvent、および ManualResetEvent が含まれます。 Set メソッドを呼び出すか、SignalAndWait メソッドを使用することにより、イベント待機ハンドルがシグナル状態になると、スレッドはそのイベント待機ハンドルから解放されます。

イベント待機ハンドルは、シグナル状態になるたびに 1 つのスレッドだけが通過できる回転ドアのように、自身を自動的にリセットするか、またはシグナル状態になるまで閉じられ、だれかが閉じるまで開いているゲートのように、手動でリセットする必要があります。 名前が示すように、AutoResetEvent は前者を、ManualResetEvent は後者を表します。 System.Threading.ManualResetEventSlim は、単一プロセスの境界内での同期に使用できる軽量なイベントです。

EventWaitHandle は、いずれかの種類のイベントを表すことができ、ローカルにもグローバルにもできます。 派生クラス AutoResetEventManualResetEvent は、常にローカルです。

イベント待機ハンドルには、スレッド アフィニティはありません。 どのスレッドでもイベント待機ハンドルをシグナル状態にできます。

概念については、「EventWaitHandle、AutoResetEvent、CountdownEvent、および ManualResetEvent」を参照してください。

Mutex クラスと Semaphore クラス

Mutex クラスと Semaphore クラスは WaitHandle から派生するため、WaitHandle の静的メソッドで使用できます。 たとえば、WaitAll メソッドを使用すると、EventWaitHandle がシグナル状態になる、Mutex が解放される、Semaphore が解放される、という 3 つの条件がすべて当てはまるまでスレッドは待機できます。 同様に、WaitAny メソッドを使用すると、スレッドはこれらの条件のいずれか 1 つが当てはまるまで待機できます。

Mutex または Semaphore では、シグナル状態は解放された状態を意味します。 いずれかの型が SignalAndWait メソッドの最初の引数として使用された場合、その型は解放されます。 スレッド アフィニティのある Mutex の場合、呼び出し元のスレッドがミューテックスを所有していなければ、例外がスローされます。 前述のとおり、セマフォにはスレッド アフィニティはありません。

バリア

Barrier クラスを使用すると、すべてのスレッドが同じポイントでブロックされて他のすべてのスレッドが完了するのを待機するように、複数のスレッドを周期的に同期できます。 バリアは、1 つ以上のスレッドでアルゴリズムの次のフェーズに進む前に別のスレッドの結果が必要である場合に便利です。 詳細については、「バリア (.NET Framework)」を参照してください。

ページのトップへ

軽量な同期型

.NET Framework 4 以降では、パフォーマンスを向上させる同期プリミティブを使用できます。この同期プリミティブでは、待機ハンドルなどの Win32 カーネル オブジェクトに可能な限り依存する場合の高い負荷を避けることができます。 通常、これらの型を使用するのは、待機時間が短い場合であり、元の同期型を使用してその結果に満足できなかった場合だけにする必要があります。 これらの軽量な型は、プロセス間の通信を必要とするシナリオでは使用できません。

ページのトップへ

SpinWait

.NET Framework 4 以降では、イベントがシグナル状態になるまで、または特定の条件が満たされるまでスレッドを待機させる必要がある場合に、System.Threading.SpinWait 構造体を使用できます。ただし、この使用は、待機ハンドルを使用するとき、または現在のスレッドをブロックするときに、必要な待機時間よりも実際の待機時間が短くなると予測される場合にする必要があります。 SpinWait を使用すると、待機しながらスピンし、指定された時間内に条件が満たされなかった場合にのみ譲渡する (待機やスリープによってなど) 短い時間を指定できます。

ページのトップへ

インタロックされた操作

インタロックされた操作は、Interlocked クラスの静的メソッドによってメモリ位置で実行される単純な分割不可能操作です。 分割不可能な操作には、加算、インクリメントとデクリメント、交換、比較に依存する条件付き交換、32 ビット プラットフォームでの 64 ビット値の読み取り操作などがあります。

メモメモ

分割不能性の保証は個々の操作に限定されます。複数の操作を 1 つの単位として実行する必要がある場合、粒度の粗い同期機構を使用する必要があります。

これらの操作はいずれもロックまたはシグナルではありませんが、ロックおよびシグナルの構築に使用できます。 インタロックされた操作は Windows オペレーティング システムにネイティブであるため、非常に高速です。

インタロックされた操作を揮発性メモリの保証で使用すると、強力な非ブロッキング同時実行を示すアプリケーションを作成できます。 ただし、高度な低水準プログラミングを必要とするため、ほとんどの目的では単純ロックが適切な選択といえます。

概念については、「インタロックされた操作」を参照してください。

ページのトップへ

参照

概念

マルチスレッド処理のためのデータの同期

Monitor

ミューテックス

Semaphore と SemaphoreSlim

待機ハンドル

インタロックされた操作

読み取り/書き込みロック

その他の技術情報

EventWaitHandle、AutoResetEvent、CountdownEvent、および ManualResetEvent

バリア (.NET Framework)

SpinWait

SpinLock