Warum hat pthread_cond_wait unerwünschte Wakeups?

Um die man-Seite zu zitieren:

Bei der Verwendung von Bedingungsvariablen gibt es immer ein Boolesches Prädikat, das mit jeder Bedingung verbundene shared Variablen enthält, die wahr ist, wenn der Thread fortfahren soll. Scheinbare Wakeups von den functionen pthread_cond_timedwait () oder pthread_cond_wait () können auftreten. Da die Rückkehr von pthread_cond_timedwait () oder pthread_cond_wait () nichts über den Wert dieses Prädikats aussagt, sollte das Prädikat bei einer solchen Rückkehr erneut ausgewertet werden.

So kann pthread_cond_wait zurückkehren, auch wenn Sie es nicht signalisiert haben. Auf den ersten Blick scheint das ziemlich grauenhaft. Es wäre wie eine function, die nach dem Zufallsprinzip den falschen Wert zurückgegeben hat oder zufällig zurückgegeben wurde, bevor sie tatsächlich eine korrekte Rückgabeanweisung erhalten hat. Es scheint wie ein großer Fehler. Aber die Tatsache, dass sie dies in der man-Seite dokumentieren wollten, anstatt es zu reparieren, würde darauf hindeuten, dass es einen legitimen Grund dafür gibt, warum pthread_cond_wait am Ende pthread_cond_wait aufwacht. Vermutlich ist etwas intrinsisch daran, wie es funktioniert, das macht es so, dass dem nicht geholfen werden kann. Die Frage ist was.

Warum kehrt pthread_cond_wait falsch zurück? Warum kann es nicht garantieren, dass es nur aufwacht, wenn es richtig signalisiert wurde? Kann jemand den Grund für sein falsches Verhalten erklären?

   

Die folgende Erklärung gibt David R. Butenhof in “Programmieren mit POSIX-Threads” (S. 80):

Scheinbare Wakeups mögen merkwürdig klingen, aber bei einigen Multiprozessorsystemen kann ein vollständig vorhersagbares Aufwecken der Bedingung alle zustandsvariablen Operationen wesentlich verlangsamen.

In der folgenden comp.programming.threads-Diskussion geht er auf das Denken hinter dem Design ein:

 Patrick Doyle schrieb: 
 > Im Artikel schrieb Tom Payne: 
 >> Kaz Kylheku schrieb: 
 >>: Es ist so, weil Implementierungen manchmal nicht vermeiden können, einzufügen 
 >>: diese unechten Wakeups;  es könnte teuer sein, sie zu verhindern. 

 >> Aber warum?  Warum ist das so schwierig?  Zum Beispiel reden wir darüber 
 >> Situationen, in denen eine Wartezeit wie ein Signal abläuft? 

 > Weißt du, ich frage mich, ob die Designer von Pthreads Logik wie diese verwendeten: 
 > Benutzer von Zustandsvariablen müssen die Bedingung beim Beenden trotzdem prüfen, 
 > Wir werden also keine zusätzliche Belastung für sie übernehmen, wenn wir das zulassen 
 > unechte Wakeups;  und da ist es denkbar, dass es unecht ist 
 > Wakeups könnte eine Implementierung schneller machen, es kann nur helfen, wenn wir 
 > erlaube ihnen. 

 > Sie haben vielleicht keine bestimmte Implementierung im Sinn gehabt. 

 Du bist eigentlich gar nicht weit weg, außer du hast es nicht weit genug getrieben. 

 Die Absicht bestand darin, korrekten / robusten Code zu erzwingen, indem Prädikatschleifen erforderlich gemacht wurden.  Das war 
 getrieben durch das nachweislich korrekte akademische Kontingent unter den "core - Threads" in 
 die Arbeitsgruppe, obwohl ich glaube nicht, dass jemand wirklich mit der Absicht nicht einverstanden war 
 Sobald sie verstanden hatten, was es bedeutete. 

 Wir folgten dieser Absicht mit mehreren Rechtfertigungsstufen.  Das erste war das 
 "religiös" mit einer Schleife schützt die Anwendung vor ihrer eigenen Unvollkommenheit 
 Programmierpraktiken.  Die zweite war, dass es nicht schwierig war, sich abstrakt vorzustellen 
 Maschinen und Implementierungscode, der diese Anforderung zur Verbesserung ausnutzen könnte 
 die performance von durchschnittlichen Bedingungsoperationen durch Optimierung der 
 Synchronisationsmechanismen. 
 / ------------------ [David.Buten ... @ compaq.com] ------------------ \ 
 |  Compaq Computer Corporation POSIX-Thread-Architekt | 
 |  Mein Buch: http://www.awl.com/cseng/titles/0-201-63392-2/ | 
 \ ----- [http://home.earthlink.net/~anneart/family/dave.html] ----- / 

Es gibt mindestens zwei Dinge, die “falsches Aufwachen” bedeuten könnten:

  • Ein Thread, der in pthread_cond_wait blockiert ist, kann von dem Anruf zurückkehren, obwohl kein Anruf zum Signalisieren oder Senden unter der Bedingung aufgetreten ist.
  • Ein Thread, der in pthread_cond_wait blockiert ist, kehrt aufgrund eines Aufrufs zum Signalisieren oder Rundsenden zurück. Nach dem erneuten Erwerb des Mutex wird jedoch festgestellt, dass das zugrunde liegende Prädikat nicht mehr wahr ist.

Der letztere Fall kann jedoch selbst dann auftreten, wenn die Implementierung der Bedingungsvariablen den ersten Fall nicht zulässt. Betrachten Sie eine Producer-Consumer-Warteschlange und drei Threads.

  • Thread 1 hat gerade ein Element entfernt und den Mutex freigegeben, und die Warteschlange ist jetzt leer. Der Thread tut was auch immer er tut mit dem Element, das er auf irgendeiner CPU erwirbt.
  • Thread 2 versucht, ein Element aus der Warteschlange zu entfernen, findet jedoch, dass die Warteschlange leer ist, wenn sie unter dem Mutex aktiviert wird, pthread_cond_wait aufruft und Blöcke im Anruf, der Signal / Broadcast erwartet, ausführt.
  • Thread 3 ruft den Mutex ab, fügt ein neues Element in die Warteschlange ein, benachrichtigt die Bedingungsvariable und gibt die Sperre frei.
  • Als Reaktion auf die Benachrichtigung von Thread 3 wird Thread 2, der auf die Bedingung wartete, für die Ausführung geplant.
  • Bevor jedoch Thread 2 in die CPU gelangt und die Warteschlangensperre ergreift, beendet Thread 1 seine aktuelle Aufgabe und kehrt für mehr Arbeit in die Warteschlange zurück. Er ruft die Warteschlangensperre ab, überprüft das Prädikat und stellt fest, dass in der Warteschlange Arbeit ist. Es entfernt den Eintrag, der Thread 3 eingefügt hat, gibt die Sperre frei und tut was auch immer er tut, mit dem Element, das Thread 3 in die Warteschlange gestellt hat.
  • Thread 2 wird jetzt in eine CPU geladen und erhält die Sperre. Wenn das Prädikat überprüft wird, wird jedoch festgestellt, dass die Warteschlange leer ist. Thread 1 “stahl” den Gegenstand, so dass das Aufwecken falsch erscheint. Thread 2 muss erneut auf die Bedingung warten.

Da Sie also das Prädikat immer unter einer Schleife überprüfen müssen, macht es keinen Unterschied, ob die zugrundeliegenden Bedingungsvariablen andere Arten von unerwünschten Wakeups haben können.

Abschnitt “Mehrfacher Erwachen durch Bedingungssignal” in pthread_cond_signal hat eine Beispielimplementierung von pthread_cond_wait und pthread_cond_signal, die unechte Wakekups beinhalten.