2012-08-25 4 views
5

http://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystifiedMonitor.Pulse & Warten - unerwartetes Verhalten

Queues:

die Bereitschaftswarteschlange ist die Sammlung von Threads, die für eine bestimmte Sperre warten. Die Monitor.Wait-Methoden führen eine andere Warteschlange ein: die Warteschlange. Dies ist erforderlich, da Warten auf einen Impuls Warten auf eine Sperre warten. Wie die Bereit-Warteschlange ist die wartende Warteschlange FIFO.

Empfohlene Muster:

Diese Warteschlangen zu unerwartetem Verhalten führen kann. Wenn ein Impuls auftritt, wird der Kopf der Warteschlange in freigegeben und der Bereit- -Warteschlange hinzugefügt. Wenn sich in der Bereitschaftswarteschlange jedoch weitere Threads befinden, werden sie von vor dem Thread abgerufen, der freigegeben wurde. Dies ist ein Problem, weil der Thread, der die Sperre erwirbt, den Status ändern kann, auf den sich der gepulste Thread stützt. Die Lösung ist eine während Zustand innerhalb des Schloss Anweisung verwenden

* Q = Queue.

Damit verstehe ich, dass, wenn ich Pulse aufrufen, es 2 Dinge, bevor es endet. Erstens entfernt es einen Thread vom wartenden Q zum fertigen Q. Zweitens lässt es einen Thread (ohne zu wissen, wer dieser Thread ist) im Ready Q das Schloss zu erhalten; Es ist egal, wer die Sperre erwirbt (der Thread, der aus dem wartenden Q oder einem Thread kommt, der aus irgendeinem Grund in dem bereiten Q war).

Wenn ich richtig darüber dann warum übt eine while vorher Monitor.Wait hilft, das Problem zu beheben (Problem - der Impuls endet, selbst wenn der Faden, der aus dem Warte Q kam nicht das Schloss erworben hat)?

A. Sag mir, wenn ich über den Zweck von Monitor.Pulse richtig bin.

B. Warum muss ich unten ein while vor Monitor.Wait

Der vollständige Code der Antwort setzen:

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); 
     } 
    } 
} 

Antwort

9

Stellen Sie sich vor Sie versuchen, einen Erzeuger/Verbraucher-Warteschlange zu schreiben - Sie Pulse jedes Mal, wenn Sie produzieren ein Artikel, und ein Verbraucher muss warten, bis ein Artikel zu konsumieren ist. Sie würden Code wie folgt schreiben:

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

Angenommen, Sie nicht die while-Schleife haben, und stattdessen schrieb:

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

Angenommen, Sie haben einen Thread schon warten, und dann noch eine Thread kurz vor der Lock-Anweisung ... dann gibt ein Producer den Monitor aus (und fügt natürlich ein Item zur Queue hinzu).

An diesem Punkt ist es durchaus machbar, dass der Thread, der noch nicht einmal an das Schloss gekommen ist, der Erste sein wird, der das Schloss erwirbt ... an dem Punkt, zu dem der "wartende" Thread das Schloss erwirbt, die Warteschlange wäre wieder leer. Mit nur einer einzigen if-Anweisung, ohne Schleife, würden Sie am Ende der Warteschlange, wenn die Warteschlange leer ist, was fehlschlagen würde.

Mit der While-Schleife warten Sie erneut, bis das nächste Element erzeugt wird. Das ist das, was Sie wirklich wollen.

+0

Vielen Dank, ich habe den vollständigen Code Ihres Beispiels geschrieben und mit Ihrer Erklärung habe ich es 90 mal getestet und ich verstehe es jetzt vollständig. Danke Agian! –