同期プリミティブの概要

.NET では、共有リソースへのアクセスを同期する場合や、スレッド相互作用を調整する場合に使用できるさまざまな型が提供されます。

重要

共有リソースでのアクセスを保護するには、同じ同期プリミティブ インスタンスを使用します。 異なる同期プリミティブ インスタンスを使用して同じリソースを保護する場合は、同期プリミティブによって提供される保護を回避します。

WaitHandle クラスと軽量の同期型

複数の .NET 同期プリミティブは System.Threading.WaitHandle クラスから派生します。このクラスでは、ネイティブ オペレーティング システムの同期ハンドルをカプセル化し、スレッド相互作用のシグナリング メカニズムを使用します。 次のようなクラスが含まれます。

  • System.Threading.Mutex。共有リソースへの排他アクセスが許可されます。 所有しているスレッドがない場合、ミューテックスはシグナル状態になります。
  • System.Threading.Semaphore。共有リソースまたはリソースのプールに同時にアクセスできるスレッドの数を制限します。 セマフォの状態は、そのカウントが 0 より大きい場合はシグナル状態に、0 の場合は非シグナル状態に設定されます。
  • System.Threading.EventWaitHandle。スレッド同期イベントを表し、シグナル状態または非シグナル状態のいずれかになります。
  • System.Threading.AutoResetEventEventWaitHandle から派生し、シグナル状態の場合、単一の待ちスレッドを解放した後、自動的に非シグナル状態にリセットされます。
  • System.Threading.ManualResetEventEventWaitHandle から派生し、シグナル状態の場合、Reset メソッドが呼び出されるまでシグナル状態のままです。

.NET Framework では、WaitHandleSystem.MarshalByRefObject から派生するため、これらの型を使用して、アプリケーション ドメインの境界を越えてスレッドのアクティビティを同期させることができます。

.NET Framework、.NET Core、.NET 5+ では、これらの型のいくつかで名前付きのシステム同期ハンドルを表すことができます。ハンドルはオペレーティング システム全体で表示され、プロセス間同期で使用できます。

詳細については、WaitHandle API リファレンスを参照してください。

軽量の同期型は、基になるオペレーティング システム ハンドルに依存しておらず、通常はパフォーマンスが向上します。 しかし、プロセス間同期には使用できません。 これらの型は、1 つのアプリケーション内のスレッド同期で使用します。

これらの型のいくつかは、WaitHandle から派生した型の代わりに使用できます。 たとえば、SemaphoreSlim は軽量であり、Semaphore の代わりに使用できます。

共有リソースへのアクセスの同期

.NET では、複数のスレッドによる共有リソースへのアクセスを制御するためのさまざまな同期プリミティブが提供されます。

Monitor クラス

System.Threading.Monitor クラスでは、リソースを識別するオブジェクトに対するロックを取得または解放することで、共有リソースへの排他アクセスを許可します。 ロックが保持されている間、ロックを保持するスレッドではロックを再度取得し、解放することができます。 他のスレッドはブロックされてロックを取得できず、Monitor.Enter メソッドではロックが解放されるまで待機します。 Enter メソッドでは解放されたロックを取得します。 また、Monitor.TryEnter メソッドを使用して、スレッドでのロック取得の試行時間を指定することもできます。 Monitor クラスにはスレッド アフィニティがあるため、ロックを取得したスレッドでは、Monitor.Exit メソッドを呼び出してロックを解放する必要があります。

Monitor.WaitMonitor.Pulse、および Monitor.PulseAll メソッドを使用して、同じオブジェクトに対するロックを取得するスレッドの相互作用を調整することができます。

詳細については、Monitor API リファレンスを参照してください。

注意

C# では lock ステートメントを、Visual Basic では SyncLock ステートメントを使って、Monitor クラスを直接使用せずに、共有リソースへのアクセスを同期します。 これらのステートメントは、Enter および Exit メソッドを使用して実装されます。また、このステートメントでは、取得されたロックが常に確実に解除されるように、try…finally ブロックが使用されます。

Mutex クラス

System.Threading.Mutex クラスでは、Monitor と同様に、共有リソースへの排他アクセスを許可します。 Mutex.WaitOne メソッドのオーバーロードのいずれかを使用して、ミューテックスの所有権を要求します。 Monitor と同様に、Mutex にはスレッド アフィニティがあり、ミューテックスを取得したスレッドで、Mutex.ReleaseMutex メソッドを呼び出してそれを解放する必要があります。

Monitor とは異なり、Mutex クラスをプロセス間同期に使用することができます。 そのためには、オペレーティング システム全体で表示される、名前付きミューテックスを使用します。 名前付きミュー テックスのインスタンスを作成するには、名前を指定する Mutex コンストラクターを使用します。 また、Mutex.OpenExisting メソッドを呼び出して、既存の名前付きシステム ミューテックスを開くこともできます。

詳細については、「ミューテックス」の記事と、Mutex API リファレンスを参照してください。

SpinLock 構造体

System.Threading.SpinLock 構造体では、Monitor と同様に、ロックの可用性に基づいて、共有リソースへの排他アクセスを許可します。 SpinLock で使用できないロックの取得が試行された場合、ループ内で待機し、ロックが使用できるようになるまで繰り返し確認されます。

スピン ロックを使用する利点と欠点の詳細については、「SpinLock」の記事と、SpinLock API リファレンスを参照してください。

ReaderWriterLockSlim クラス

System.Threading.ReaderWriterLockSlim クラスでは、書き込み用の共有リソースへの排他アクセスを許可し、複数のスレッドでの読み取り用のリソースへの同時アクセスを許可します。 スレッドセーフ読み取り操作をサポートするが、書き込み操作を行うために排他アクセスを必要とする共有データ構造体へのアクセスを同期する場合は、ReaderWriterLockSlim を使用できます。 スレッドで (たとえば、ReaderWriterLockSlim.EnterWriteLock メソッドを呼び出して) 排他アクセスを要求すると、後続のリーダーとライターの要求は、既存のすべてのリーダーがロックを終了し、ライターがロックに参加して終了するまでブロックされます。

詳細については、ReaderWriterLockSlim API リファレンスを参照してください。

Semaphore および SemaphoreSlim クラス

System.Threading.Semaphore および System.Threading.SemaphoreSlim クラスでは、共有リソースまたはリソースのプールに同時にアクセスできるスレッドの数を制限します。 リソースを要求する追加のスレッドは、任意のスレッドでセマフォが解放されるまで待機します。 セマフォにはスレッド アフィニティがないため、あるスレッドでセマフォを取得し、別のスレッドで解放することができます。

SemaphoreSlim は軽量であり、Semaphore の代わりに使用され、単一のプロセス境界内の同期化でのみ使用できます。

Windows では、プロセス間同期で Semaphore を使用できます。 そのためには、名前または Semaphore.OpenExisting メソッドを指定する Semaphore コンストラクターのいずれかを使用して、名前付きシステム セマフォを表す Semaphore インスタンスを作成します。 SemaphoreSlim では、名前付きシステム セマフォはサポートされていません。

詳細については、「Semaphore と SemaphoreSlim」の記事と、Semaphore または SemaphoreSlim API リファレンスを参照してください。

スレッドの相互作用、またはシグナリング

スレッドの相互作用 (またはスレッドのシグナリング) は、あるスレッドで、先に進むために 1 つ以上のスレッドからの通知 (または、シグナル) を待機する必要があることを意味します。 たとえば、スレッド A でスレッド B のThread.Join メソッドを呼び出す場合、スレッド A は、スレッド B が完了するまでブロックされます。 前のセクションで説明されている同期プリミティブでは、シグナリングに関する別のメカニズムが提供されます。その場合、あるスレッドによって、ロックを獲得することで先に進むことができる別のスレッドに通知されます。

このセクションでは、.NET によって提供される追加のシグナリング構造について説明します。

EventWaitHandle、AutoResetEvent、ManualResetEvent、および ManualResetEventSlim クラス

System.Threading.EventWaitHandle クラスはスレッドの同期イベントを表します。

同期イベントには、非シグナル状態またはシグナル状態のいずれかを指定できます。 イベントの状態が非シグナルの場合、イベントの WaitOne オーバーロードを呼び出すスレッドは、イベントがシグナル状態になるまでブロックされます。 EventWaitHandle.Set メソッドでは、イベントの状態をシグナル状態に設定します。

シグナル状態になった EventWaitHandle の動作は、そのリセット モードによって異なります。

Windows では、プロセス間同期で EventWaitHandle を使用できます。 そのためには、名前または EventWaitHandle.OpenExisting メソッドを指定する EventWaitHandle コンストラクターのいずれかを使用して、名前付きシステム同期イベントを表す EventWaitHandle インスタンスを作成します。

詳細については、「EventWaitHandle」の記事を参照してください。 API リファレンスについては、EventWaitHandleAutoResetEventManualResetEvent、および ManualResetEventSlim に関する記述を参照してください。

CountdownEvent クラス

System.Threading.CountdownEvent クラスは、そのカウントが 0 になると設定されるようになるイベントを表します。 CountdownEvent.CurrentCount が 0 より大きい間は、CountdownEvent.Wait を呼び出すスレッドがブロックされます。 イベントのカウントをデクリメントするには、CountdownEvent.Signal を呼び出します。

1 つのスレッドからのシグナルで複数のスレッドのブロックを解除するために使用できる、ManualResetEventManualResetEventSlim とは対照的に、CountdownEvent を使用すると、複数のスレッドからのシグナルで 1 つ以上のスレッドのブロックを解除できます。

詳細については、「CountdownEvent」の記事と、CountdownEvent API リファレンスを参照してください。

Barrier クラス

System.Threading.Barrier クラスはスレッド実行のバリアを表します。 Barrier.SignalAndWait メソッドを呼び出すスレッドでは、バリアに到達したことを知らせ、他の参加スレッドがバリアに到達するまで待機します。 すべての参加スレッドは、バリアに到達したときに先に進み、バリアはリセットされて再度使用することができます。

1 つ以上のスレッドが次の計算フェーズに進む前に、他のスレッドの結果を必要とする場合は、Barrier を使用できます。

詳細については、「バリア」の記事と、Barrier API リファレンスを参照してください。

Interlocked クラス

System.Threading.Interlocked クラスでは、変数に対してシンプルなアトミック操作を実行する静的メソッドが提供されます。 それらのアトミック操作としては、64 ビット整数値を対象とした追加、インクリメントとデクリメント、交換、比較による条件付き交換、読み取りの各操作があります。

詳細については、Interlocked API リファレンスを参照してください。

SpinWait 構造体

System.Threading.SpinWait 構造体では、スピンベースの待機のサポートが提供されます。 これは、イベントが通知されるか条件が満たされるのをスレッドが待機する必要があるが、待機ハンドルを使用するかあるいはスレッドをブロックする際に必要な待機時間よりも実際の待機時間が短いと思われる場合に使用できます。 SpinWait を使用することにより、待機中はスピンし、指定した時間内に条件が満たされなかった場合のみ (たとえば待機またはスリープして) 譲渡するための短い時間を指定できます。

詳細については、「SpinWait」の記事と、SpinWait API リファレンスを参照してください。

関連項目