2012-08-25 16 views
5

http://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystifiedMonitor.Pulse&ウェイト - 予期しない動作

キュー:

レディキューは 特定のロックのために待機しているスレッドのコレクションです。 Monitor.Waitメソッドは、別のキュー、 待機キューを導入します。これは、パルスが別個の がロックを獲得するのを待つのを待つときに必要です。準備完了キューと同様に、待ちキュー はFIFOです。

推奨パターン:

これらのキューは予期しない動作を引き起こす可能性があります。パルスが発生すると、待ちキューの先頭の が解放され、準備完了キュー に追加されます。ただし、レディキューに他のスレッドがある場合は、 は解放されたスレッドの前にロックを取得します。ロックを取得するスレッドは、パルススレッドが依存する状態 を変更できるため、これは の問題です。解決策は、ロック文内にある 条件を使用することです。

* Q =キュー。

それで、私はPulseと呼んだとき、それが終わる前に2つのことをすることを理解しています。まず、待機中のQからREADY Qへのスレッドを1つ取り除きます。次に、Ready Qの1つのスレッド(そのスレッドが誰であるかを知らないで)がロックを取得するようにします。誰がロックを取得したか(何らかの理由で待機中のQから来たスレッドまたは準備完了したQにあったスレッド)は気にしません。

私はそのことについて権利だ場合は、なぜは、問題を解決する手助けMonitor.Waitwhileを入れている(問題 - パルスが待機Qから来たスレッドがロックを取得していない場合でも終了)?

Monitor.Pulseの目的について私が正しいかどうか教えてください。

b。もしあなたが作るPulseたびに - あなたはプロデューサー/コンシューマ・キューを作成しようとしている想像し

class Program 
{ 
    static Queue<int> queue = new Queue<int>(); 
    static object someMonitor = new object(); 

    static void Main(string[] args) 
    { 
     Thread Thread1 = new Thread(WorkAlltheTime); 
     Thread1.Name = "Thread1"; 
     Thread Thread2 = new Thread(WorkAlltheTime); 
     Thread2.Name = "Thread2"; 
     Thread Thread3 = new Thread(WorkOnce); 
     Thread3.Name = "Thread3"; 
     Thread1.Start(); 
     Thread2.Start(); 
     Thread.Sleep(1000); 
     Thread3.Start(); 
     Console.ReadLine(); 
    } 

    static void WorkAlltheTime() 
    { 
     Console.WriteLine("Came in to Ready Q: " + Thread.CurrentThread.Name); 
     lock (someMonitor) 
     { 
      Console.WriteLine("Came out from Ready Q: " + Thread.CurrentThread.Name); 
      // Broken! 
      while (queue.Count == 0) 
      { 
       Console.WriteLine("Came in to Waiting Q: " + Thread.CurrentThread.Name); 
       Monitor.Wait(someMonitor); 
       Console.WriteLine("Came out from Waiting Q: " + Thread.CurrentThread.Name); 
      } 
      queue.Dequeue(); 
      Console.WriteLine("Thread: "+Thread.CurrentThread.Name+" Pulled Out"); 
     } 
    } 

    static void WorkOnce() 
    { 
     lock (someMonitor) 
     { 
      queue.Enqueue(1); 
      Monitor.Pulse(someMonitor); 
     } 
    } 
} 

答えて

9

は、なぜ私は以下の回答のwhile

Monitor.Waitの前に完全なコードを配置する必要がありますアイテム、および消費者は、消費するアイテムがあるまで待つ必要があります。あなたはこのようなコードを記述します

Foo item; 
lock(someMonitor) 
{ 
    while (queue.Count == 0) 
    { 
     Monitor.Wait(someMonitor); 
    } 
    item = queue.Dequeue(); 
} 
// Use the item 

あなたはは、whileループを持っていなかったと仮定し、代わりに書いている:

Foo item; 
lock(someMonitor) 
{ 
    // Broken! 
    if (queue.Count == 0) 
    { 
     Monitor.Wait(someMonitor); 
    } 
    item = queue.Dequeue(); 
} 
// Use the item 

は今、あなたは一つのスレッドがすでに待っているとし、その後、別のスレッドをロックステートメントの直前に置くと、プロデューサはモニタをパルスします(もちろん、キューにアイテムを追加します)。

その時点では、まだロックに達していないスレッドも、最初にロックを取得するスレッドであることは完全に実現可能です...「待機中の」スレッドがロックを獲得する時点で、キューは再び空になります。単一のifステートメントだけをループすることなく、キューが空になるとデキューしてしまい、失敗することになります。

whileループを使用すると、次のアイテムが作成されるまで待つことになります。これは本当に必要なものです。

+0

ありがとう、私はあなたの例の完全なコードを書いて、あなたの説明で私は90回それをテストし、私は今完全に理解しています。ありがとう、アギアン! –