いつセマフォを使用し、いつ条件変数(CondVar)を使用する必要がありますか?
ロックは相互排除に使用されます。コードの一部がアトミックであることを確認したい場合は、ロックをかけます。理論的にはバイナリセマフォを使用してこれを行うことができますが、それは特別な場合です。
セマフォと条件変数は、ロックによって提供される相互排除の上に構築され、共有リソースへの同期アクセスを提供するために使用されます。それらは同様の目的に使用できます。
一般に、条件変数は、リソースが使用可能になるのを待っている間にビジーな待機(条件の確認中に繰り返しループする)を避けるために使用されます。たとえば、キューが空になるまで先に進むことができないスレッド(または複数のスレッド)がある場合、ビジー待機アプローチは次のようなことを行うことです。
_//pseudocode
while(!queue.empty())
{
sleep(1);
}
_
これに関する問題は、このスレッドに条件を繰り返しチェックさせることにより、プロセッサー時間を浪費していることです。代わりに、リソースが利用可能であることをスレッドに知らせるために信号を送ることができる同期変数を持たないのはなぜですか?
_//pseudocode
syncVar.lock.acquire();
while(!queue.empty())
{
syncVar.wait();
}
//do stuff with queue
syncVar.lock.release();
_
おそらく、キューから何かを引き出すスレッドが他のどこかにあるでしょう。キューが空の場合、syncVar.signal()
を呼び出して、syncVar.wait()
でスリープ状態になっているランダムなスレッドを起動できます(または、通常はsignalAll()
またはbroadcast()
メソッドは、待機中のすべてのスレッドを起動します)。
単一の特定の条件で待機している1つ以上のスレッドがある場合(キューが空になるなど)、通常、このような同期変数を使用します。
セマフォも同様に使用できますが、整数個の使用可能なものに基づいて使用可能および使用不可にできる共有リソースがある場合、セマフォはよりよく使用されると思います。セマフォは、生産者がリソースを割り当て、消費者がリソースを消費する生産者/消費者の状況に適しています。
ソーダの自動販売機があるかどうかを考えてください。ソーダマシンは1つしかなく、共有リソースです。マシンの在庫を管理するベンダー(プロデューサー)であるスレッドと、マシンからソーダを取り出したい買い手(コンシューマー)であるN個のスレッドがあります。マシン内のソーダの数は、セマフォを駆動する整数値です。
ソーダマシンに来るすべてのバイヤー(コンシューマ)スレッドは、セマフォdown()
メソッドを呼び出してソーダを取得します。これにより、マシンからソーダが取得され、使用可能なソーダの数が1つ減ります。使用可能なソーダがある場合、コードはdown()
ステートメントを問題なく実行し続けます。ソーダが利用できない場合、スレッドはここでスリープし、ソーダが再び利用可能になったとき(マシンにさらにソーダがあるとき)の通知を待ちます。
ベンダー(プロデューサー)スレッドは、ソーダマシンが空になるのを本質的に待っています。ベンダーは、最後のソーダがマシンから取り出されると通知を受けます(1人以上の消費者がソーダの取り出しを待機している可能性があります)。ベンダーは、ソーダマシンにセマフォup()
メソッドを補充します。使用可能なソーダの数は毎回増加し、それにより待機中のコンシューマスレッドは、さらにソーダが使用可能であることを通知されます。
同期変数のwait()
およびsignal()
メソッドは、セマフォのdown()
およびup()
操作内に隠される傾向があります。
確かに、2つの選択肢には重複があります。セマフォまたは条件変数(または条件変数のセット)の両方が目的に役立つ多くのシナリオがあります。セマフォと条件変数は、相互排除を維持するために使用するロックオブジェクトに関連付けられますが、スレッド実行を同期するためのロックの追加機能を提供します。どちらがあなたの状況に最も理にかなっているのかを判断するのは、ほとんどあなた次第です。
それは必ずしも最も技術的な説明ではありませんが、それは私の頭の中で理にかなっています。
ボンネットの下にあるものを明らかにしましょう。
条件変数は基本的に待機キューです、これはブロッキング待機およびウェイクアップ操作をサポートします。つまり、スレッドを待機キューに入れ、その状態をBLOCKに設定し、そこからスレッドを取得できます。状態をREADYに設定します。
条件変数を使用するには、他に2つの要素が必要であることに注意してください。
プロトコルは次のようになります。
セマフォは本質的にはカウンター+ミューテックス+待機キューです。そして、外部の依存関係なしでそのまま使用できます。ミューテックスまたは条件変数として使用できます。
したがって、セマフォは条件変数よりも洗練された構造として扱うことができ、後者はより軽量で柔軟です。
セマフォは変数への排他的アクセスを実装するために使用できますが、同期のために使用することを意図しています。一方、ミューテックスには相互排他に厳密に関連するセマンティクスがあります。リソースをロックしたプロセスのみがロックを解除できます。
残念ながら、ミューテックスとの同期を実装することはできません。そのため、条件変数があります。また、条件変数を使用すると、ブロードキャストのロック解除を使用して、同じ瞬間にすべての待機スレッドをロック解除できることに注意してください。これはセマフォでは実行できません。
セマフォと条件変数は非常に似ており、ほとんど同じ目的で使用されます。ただし、1つが望ましいと思われる小さな違いがあります。たとえば、バリア同期を実装するには、セマフォを使用できませんが、条件変数が理想的です。
バリア同期とは、スレッド関数の特定の部分に全員が到着するまで、すべてのスレッドを待機させたい場合です。これは、各スレッドがそのバリアに到達したときに最初に減少する合計スレッドの値である静的変数を持つことで実装できます。これは、最後のスレッドが到着するまで各スレッドをスリープさせることを意味します。セマフォはまったく逆のことを行います!セマフォを使用すると、各スレッドは実行を続け、最後のスレッド(セマフォの値を0に設定します)はスリープ状態になります。
一方、条件変数は理想的です。各スレッドがバリアに到達すると、静的カウンタがゼロかどうかを確認します。そうでない場合は、条件変数wait関数を使用してスレッドをスリープ状態に設定します。最後のスレッドがバリアに到達すると、カウンター値はゼロまでデクリメントされ、この最後のスレッドは他のすべてのスレッドを起動する条件変数シグナル関数を呼び出します!
モニター同期の下で条件変数をファイルします。セマフォとモニターは、2つの異なる同期スタイルとして一般的に見てきました。本質的に状態データを保持する量とコードのモデル化方法の点で2つの間に違いがありますが、実際には一方では解決できるが他方では解決できない問題はありません。
私はモニターフォームに向けてコーディングする傾向があります。私が働いているほとんどの言語では、ミューテックス、条件変数、およびいくつかのバッキング状態変数になります。しかし、セマフォもその仕事をします。