この Monitor.Wait/Pulse の使用には競合状態が発生しますか?
-
26-09-2019 - |
質問
私は単純な生産者/消費者のシナリオを持っており、生産/消費されるアイテムは 1 つだけです。また、プロデューサーはワーカー スレッドが終了するのを待ってから続行します。それがマルチスレッドの本質を台無しにしてしまうことは承知していますが、本当にそうする必要があると考えてください (:
このコードはコンパイルできませんが、理解していただければ幸いです。
// m_data is initially null
// This could be called by any number of producer threads simultaneously
void SetData(object foo)
{
lock(x) // Line A
{
assert(m_data == null);
m_data = foo;
Monitor.Pulse(x) // Line B
while(m_data != null)
Monitor.Wait(x) // Line C
}
}
// This is only ever called by a single worker thread
void UseData()
{
lock(x) // Line D
{
while(m_data == null)
Monitor.Wait(x) // Line E
// here, do something with m_data
m_data = null;
Monitor.Pulse(x) // Line F
}
}
よくわからない状況は次のとおりです。
多くのスレッドが異なる入力で SetData() を呼び出すとします。そのうちの 1 つだけがロック内に侵入し、残りはライン A でブロックされます。ロックの中に侵入したものが設定されたと仮定します m_data そしてC線へ向かいます。
質問:C 行の Wait() により、A 行の別のスレッドがロックを取得して上書きできるようになりますか? m_data ワーカースレッドがそれに到達する前に?
それが起こらず、ワーカースレッドが元のスレッドを処理すると仮定します。 m_data, 、最終的にライン F に到達します。その Pulse() がオフになると何が起こるでしょうか?
C 行で待機しているスレッドだけがロックを取得できるのでしょうか?それとも、ライン A で待機している他のすべてのスレッドと競合するのでしょうか?
基本的に、Pulse()/Wait() が特別に「内部」で相互に通信しているのか、それとも lock() と同じレベルにあるのかを知りたいのです。
これらの問題が存在する場合、その解決策はもちろん明白です。SetData() を別のロック、たとえば lock(y) で囲むだけです。そもそもそれが問題なのかどうかだけが気になります。
解決
コンシューマが別のプロデューサより先に待機キューまたは準備完了キューに登録されるという保証はありません。
C# および Java スタイルのモニターについては、次の場所で説明されています。 ウィキペディアの「暗黙的条件モニター」の下.
で何が起こっているかについての概要 Monitor
(から抜粋 これ 素晴らしいサイト):
「行 C の Wait() により、行 A の別のスレッドがロックを取得し、ワーカー スレッドがロックに到達する前に m_data を上書きできるでしょうか?」
仮定 SetData()
2 つのプロデューサー スレッドによって呼び出されます。 P1 & P2.
消費者向けスレッド、 C1 も開始されています。
P1, P2 そして C1 全員が準備完了キューに入ります。
P1 最初にロックを取得します。
待機列は空ですが、 Pulse()
の上 line B
効果はありません。
P1 待っています line C
, そのため、待機キューに入れられます。
レディキュー内の次のスレッドがロックを取得します。
同様にあり得ます P2 または C1 - 最初のケースでは、アサーションは失敗します。
競合状態が発生しています。
「それが起こらず、ワーカー スレッドが元の m_data を処理し、最終的に行 F に到達すると仮定します。その Pulse() がオフになると何が起こるでしょうか?」
ウェイターを待機キューから準備完了キューに移動します。
ロックは、 Pulse()
.
通知されたスレッドは、 チャンスをつかむ パルスを発するスレッドがロックを解放した後にロックを取得します (準備完了キューにすでに他のスレッドが存在する可能性があります)。
から MSDN、Monitor.Pulse():
「指定されたオブジェクトのロックを現在所有しているスレッドは、このメソッドを呼び出して、ロックの順番にある次のスレッドに信号を送ります。パルスを受信すると、待機中のスレッドは準備完了キューに移動されます。Pulse を呼び出したスレッドがロックを解放すると、準備完了キュー内の次のスレッド (必ずしもパルスされたスレッドであるとは限りません) がロックを取得します。
「ライン C で待機しているスレッドだけがロックを取得できるのでしょうか?それとも、ライン A で待機している他のすべてのスレッドと競合するのでしょうか?」
レディキューにあるすべてのスレッドは、次にロックを取得するために「競合」します。
彼らが直接そこに到着したか、それとも待機列から何らかの手段で到着したかは関係ありません。 Pulse()
.
「キュー」は他の手段で実装される場合があります。(そうではありません 列 データ構造自体)。
このようにして、 Monitor
実装では公平性が保証されない可能性がありますが、全体的なスループット/パフォーマンスは向上する可能性があります。