Warum hat pthread_cond_wait falsche Weckrufe?

144

Um die Manpage zu zitieren:

Bei Verwendung von Bedingungsvariablen gibt es immer ein boolesches Prädikat mit gemeinsam genutzten Variablen, die jeder Bedingungswartezeit zugeordnet sind. Dies ist der Fall, wenn der Thread fortgesetzt werden soll. Es können falsche Aufweckvorgänge mit den Funktionen pthread_cond_timedwait () oder pthread_cond_wait () auftreten. Da die Rückgabe von pthread_cond_timedwait () oder pthread_cond_wait () nichts über den Wert dieses Prädikats aussagt, sollte das Prädikat bei einer solchen Rückgabe neu bewertet werden.

So pthread_cond_waitkann zurückkehren , auch wenn Sie es nicht signalisiert haben. Zumindest auf den ersten Blick scheint das ziemlich grausam. Es wäre wie eine Funktion, die zufällig den falschen Wert oder zufällig zurückgegeben hat, bevor sie tatsächlich eine richtige return-Anweisung erreicht hat. Es scheint ein großer Fehler zu sein. Aber die Tatsache, dass sie sich dafür entschieden haben, dies auf der Manpage zu dokumentieren, anstatt es zu korrigieren, scheint darauf hinzudeuten, dass es einen legitimen Grund gibt, warum pthread_cond_waitsie falsch aufwachen. Vermutlich hat die Funktionsweise etwas Eigenes, das dazu beiträgt, dass dem nicht geholfen werden kann. Die Frage ist was.

Warum ist pthread_cond_waitspuriously zurückkehren? 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?

Jonathan M Davis
quelle
5
Ich würde mir vorstellen, dass es etwas mit der Rückkehr zu tun hat, wenn der Prozess ein Signal empfängt. Die meisten * nixes starten einen blockierenden Anruf nicht neu, nachdem ein Signal ihn unterbrochen hat. Sie setzen / geben nur einen Fehlercode zurück, der besagt, dass ein Signal aufgetreten ist.
CHao
1
@cHao: Obwohl zu beachten ist, dass die Behandlung eines Signals kein Fehler ist, weil Bedingungsvariablen ohnehin andere Gründe für ein falsches Aufwecken haben pthread_cond_(timed)wait: "Wenn ein Signal geliefert wird ... setzt der Thread das Warten auf die Bedingungsvariable fort, als ob es wäre nicht unterbrochen, oder es wird aufgrund eines falschen Aufwachens Null zurückgegeben ". Andere Sperrfunktionen zeigen an, EINTRwenn sie durch ein Signal unterbrochen werden (z. B. read) oder fortgesetzt werden müssen (z pthread_mutex_lock. B. ). Wenn es also keine anderen Gründe für ein falsches Aufwachen gäbe, pthread_cond_waithätte dies wie einer der beiden definiert werden können.
Steve Jessop
4
Ein verwandter Artikel auf Wikipedia: Spurious Wakeup
Palec
3
Nützliche Vladimir Prus: Spurious Wakeups .
Iammilind
Viele Funktionen können ihre Arbeit nicht vollständig ausführen (unterbrochene E / A), und Beobachtungsfunktionen können kein Ereignis wie eine Änderung in einem Verzeichnis empfangen, in dem die Änderung abgebrochen oder zurückgesetzt wurde. Was ist das Problem?
Neugieriger

Antworten:

77

Die folgende Erklärung wird von David R. Butenhof in "Programmieren mit POSIX-Threads" (S. 80) gegeben:

Falsche Aufweckvorgänge mögen seltsam klingen, aber auf einigen Multiprozessorsystemen kann eine vollständige Vorhersagbarkeit des Aufweckens von Zuständen alle Operationen mit Bedingungsvariablen erheblich verlangsamen.

In der folgenden Diskussion zu comp.programming.threads erweitert er das Denken hinter dem Design:

Patrick Doyle schrieb: 
> In einem Artikel schrieb Tom Payne: 
>> Kaz Kylheku schrieb: 
>>: Dies liegt daran, dass Implementierungen das Einfügen manchmal nicht vermeiden können 
>>: diese falschen Weckrufe; es könnte teuer sein, sie zu verhindern.

>> Aber warum? Warum ist das so schwierig? Sprechen wir zum Beispiel über
>> Situationen, in denen eine Wartezeit abläuft, sobald ein Signal eintrifft? 

> Weißt du, ich frage mich, ob die Designer von pthreads Logik wie diese verwendet haben: 
> Benutzer von Bedingungsvariablen müssen die Bedingung beim Beenden trotzdem überprüfen, 
> Wir werden sie also nicht zusätzlich belasten, wenn wir dies zulassen 
> falsches Aufwachen; und da ist es denkbar, dass man falsch zulässt
> Wakeups könnten eine Implementierung beschleunigen, es kann nur helfen, wenn wir 
> erlaube ihnen. 

> Möglicherweise hatten sie keine bestimmte Implementierung im Sinn. 

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

Die Absicht war, korrekten / robusten Code zu erzwingen, indem Prädikatschleifen benötigt wurden. Das war
getrieben von dem nachweislich korrekten akademischen Kontingent unter den "Core Threadies" in 
die Arbeitsgruppe, obwohl ich nicht glaube, dass jemand wirklich mit der Absicht nicht einverstanden war 
Sobald sie verstanden hatten, was es bedeutete. 

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

NPE
quelle
22
Im Grunde sagt das nichts. Hier wird keine andere Erklärung gegeben als der anfängliche Gedanke, dass "es die Dinge schneller machen kann", aber niemand weiß, wie oder ob es überhaupt funktioniert.
Bogdan Ionitza
107

Es gibt mindestens zwei Dinge, die "falsches Aufwachen" bedeuten könnte:

  • Ein blockierter Thread pthread_cond_waitkann vom Anruf zurückkehren, obwohl kein Anruf an pthread_call_signaloder pthread_cond_broadcastunter der Bedingung aufgetreten ist.
  • Ein Thread, der pthread_cond_waitaufgrund eines Aufrufs von pthread_cond_signaloder in zurückgegeben wird pthread_cond_broadcast, wird jedoch nach dem erneuten Abrufen des Mutex als nicht mehr wahr eingestuft.

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

  • Thread 1 hat gerade ein Element aus der Warteschlange entfernt und den Mutex freigegeben, und die Warteschlange ist jetzt leer. Der Thread macht alles, was er mit dem Element macht, das er auf einer CPU erfasst hat.
  • Thread 2 versucht, ein Element aus der Warteschlange zu entfernen, stellt jedoch fest, dass die Warteschlange leer ist, wenn sie unter Mutex, Aufrufen pthread_cond_waitund Blöcken in dem auf Signal / Broadcast wartenden Anruf überprüft wird .
  • Thread 3 erhält den Mutex, fügt ein neues Element in die Warteschlange ein, benachrichtigt die Bedingungsvariable und gibt die Sperre frei.
  • In Reaktion auf die Benachrichtigung von Thread 3 wird die Ausführung von Thread 2 geplant, der auf die Bedingung gewartet hat.
  • Bevor es Thread 2 jedoch schafft, in die CPU zu gelangen und die Warteschlangensperre zu aktivieren, beendet Thread 1 seine aktuelle Aufgabe und kehrt zur weiteren Arbeit in die Warteschlange zurück. Es erhält die Warteschlangensperre, überprüft das Prädikat und stellt fest, dass sich Arbeit in der Warteschlange befindet. Es wird fortgefahren, das Element, das Thread 3 eingefügt hat, aus der Warteschlange zu entfernen, die Sperre aufzuheben und alles zu tun, was es mit dem Element tut, das Thread 3 in die Warteschlange gestellt hat.
  • Thread 2 gelangt nun in eine CPU und erhält die Sperre. Wenn er jedoch das Prädikat überprüft, stellt er fest, dass die Warteschlange leer ist. Thread 1 hat den Gegenstand 'gestohlen', so dass das Aufwecken falsch erscheint. Thread 2 muss erneut auf die Bedingung warten.

Da Sie das Prädikat bereits immer unter einer Schleife überprüfen müssen, spielt es keine Rolle, ob die zugrunde liegenden Bedingungsvariablen andere Arten von falschen Aufwecken aufweisen können.

acm
quelle
23
Ja. Dies ist im Wesentlichen der Fall, wenn ein Ereignis anstelle eines Synchronisationsmechanismus mit einer Zählung verwendet wird. Leider scheint es, dass POSIX-Semaphoren (jedenfalls unter Linux) ebenfalls Spurius-Weckvorgängen unterliegen. Ich finde es nur ein bisschen seltsam, dass ein grundlegender Funktionsfehler von Synchronisationsprimitiven nur als "normal" akzeptiert wird und auf Benutzerebene umgangen werden muss :( Vermutlich wären Entwickler in der Lage, wenn ein Systemaufruf dokumentiert würde mit einem Abschnitt 'Spurious Segfault' oder vielleicht 'Spurious Connecting to the Falsche URL' oder 'Spurious Öffnen der falschen Datei'.
Martin James
2
Das häufigere Szenario eines "falschen Aufwachens" ist höchstwahrscheinlich der Nebeneffekt eines Aufrufs von pthread_cond_broadcast (). Angenommen, Sie haben einen Pool von 5 Threads, zwei wachen zur Sendung auf und erledigen die Arbeit. Die anderen drei wachen auf und stellen fest, dass die Arbeit erledigt ist. Multiprozessorsysteme können auch dazu führen, dass ein bedingtes Signal versehentlich mehrere Threads aufweckt. Der Code überprüft das Prädikat nur erneut, erkennt einen ungültigen Status und geht wieder in den Ruhezustand. In beiden Fällen wird das Problem durch Überprüfen des Prädikats behoben. IMO sollten Benutzer im Allgemeinen keine rohen POSIX-Mutexe und -Bedingungen verwenden.
CubicleSoft
1
@MartinJames - Wie wäre es mit dem klassischen "falschen" EINTR? Ich werde zustimmen, dass das ständige Testen auf EINTR in einer Schleife ein bisschen nervig ist und Code ziemlich hässlich macht, aber Entwickler tun es trotzdem, um zufällige Brüche zu vermeiden.
CubicleSoft
2
@Yola Nein, das kann es nicht, weil du einen Mutex um das sperren pthread_cond_signal/broadcastsollst und dies nicht kannst, bis der Mutex durch einen Anruf entsperrt wird pthread_cond_wait.
a3f
1
Das Beispiel dieser Antwort ist sehr realistisch und ich stimme zu, dass das Überprüfen von Prädikaten eine gute Idee ist. Konnte es jedoch nicht gleichermaßen behoben werden, indem der problematische Schritt "Thread 1 beendet seine aktuelle Aufgabe und kehrt zur weiteren Arbeit in die Warteschlange zurück" ausgeführt und durch "Thread 1 beendet seine aktuelle Aufgabe" ersetzt wird und wieder gewartet wird die Bedingungsvariable "? Das würde den in der Antwort beschriebenen Fehlermodus beseitigen, und ich bin mir ziemlich sicher, dass der Code ohne falsche Aufweckvorgänge korrekt sein würde . Gibt es eine tatsächliche Implementierung, die in der Praxis zu falschen Aufwecken führt?
Quuxplusone
7

Der Abschnitt "Mehrfaches Erwachen durch Bedingungssignal" in pthread_cond_signal enthält eine Beispielimplementierung von pthread_cond_wait und pthread_cond_signal, die falsche Wakekups beinhaltet.

Jingguo Yao
quelle
2
Ich denke, diese Antwort ist falsch, soweit es geht. Die Beispielimplementierung auf dieser Seite enthält die Implementierung "Notify One", die "Notify All" entspricht. aber es scheint keine wirklich falschen Weckrufe zu erzeugen . Die einzige Möglichkeit, einen Thread aufzuwecken, besteht darin, dass ein anderer Thread "Alle benachrichtigen" aufruft oder dass ein anderer Thread das mit "Beschriften" bezeichnete Element aufruft, das wirklich "Alle benachrichtigen" ist.
Quuxplusone
5

Obwohl ich nicht denke, dass dies zum Zeitpunkt des Entwurfs in Betracht gezogen wurde, gibt es hier einen tatsächlichen technischen Grund: In Kombination mit der Thread-Löschung gibt es Bedingungen, unter denen die Option, "falsch" aufzuwachen, absolut notwendig sein kann, zumindest wenn Sie dies nicht tun sind bereit, sehr, sehr starke Einschränkungen für die möglichen Implementierungsstrategien aufzuerlegen.

Das Hauptproblem besteht darin, dass, wenn ein Thread beim Blockieren auf die Löschung einwirkt pthread_cond_wait, die Nebenwirkungen so sein müssen, als hätte er kein Signal für die Bedingungsvariable verbraucht. Es ist jedoch schwierig (und sehr einschränkend) sicherzustellen, dass Sie noch kein Signal verbraucht haben, wenn Sie mit der Löschung beginnen. In diesem Stadium ist es möglicherweise unmöglich, das Signal erneut in die Bedingungsvariable zu "posten", da dies möglich ist in einer Situation sein, in der der Anrufer von pthread_cond_signalbereits berechtigt ist, die Kondvar zerstört und die Erinnerung, in der sie sich befand, freigegeben zu haben.

Die Berücksichtigung von falschem Wake macht es Ihnen leicht. Anstatt weiterhin auf die Stornierung zu reagieren, wenn sie eingeht, während sie für eine Bedingungsvariable blockiert ist, können Sie stattdessen ein falsches Wake deklarieren, wenn Sie möglicherweise bereits ein Signal verbraucht haben (oder wenn Sie faul sein möchten, egal was passiert). und mit Erfolg zurückkehren. Dies stört den Vorgang der Stornierung überhaupt nicht, da ein korrekter Anrufer beim nächsten Schleifen und pthread_cond_waiterneuten Anrufen einfach auf die anstehende Stornierung reagiert .

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle