Warum hat pthread_cond_wait falsche Wakeups?

Lesezeit: 10 Minuten

Um die Manpage zu zitieren:

Bei der Verwendung von Bedingungsvariablen gibt es immer ein boolesches Prädikat mit gemeinsam genutzten Variablen, die jeder Bedingung zugeordnet sind, die wahr ist, wenn der Thread fortfahren soll. Es kann zu falschen Aktivierungen durch die Funktionen pthread_cond_timedwait() oder pthread_cond_wait() kommen. Da die Rückgabe von pthread_cond_timedwait() oder pthread_cond_wait() nichts über den Wert dieses Prädikats impliziert, sollte das Prädikat bei einer solchen Rückgabe neu bewertet werden.

So, pthread_cond_wait zurückkehren kann, auch wenn Sie es nicht signalisiert haben. Zumindest auf den ersten Blick scheint das ziemlich grausam zu sein. Es wäre wie eine Funktion, die zufällig den falschen Wert zurückgibt oder zufällig zurückgibt, bevor sie tatsächlich eine richtige return-Anweisung erreicht. Es scheint ein großer Fehler zu sein. Aber die Tatsache, dass sie sich dafür entschieden haben, dies in der Manpage zu dokumentieren, anstatt es zu beheben, scheint darauf hinzudeuten, dass es einen legitimen Grund dafür gibt pthread_cond_wait wacht schließlich fälschlicherweise auf. Vermutlich gibt es etwas intrinsisches an der Funktionsweise, das es so macht, dass dem nicht geholfen werden kann. Die Frage ist was.

Warum tut pthread_cond_wait falsch zurückgeben? 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?

  • Ich könnte 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

    21. Dezember 2011 um 18:34 Uhr


  • @cHao: Beachten Sie jedoch, dass Bedingungsvariablen vorhanden sind Sonstiges Gründe für falsches Aufwachen sowieso, der Umgang mit einem Signal ist kein Fehler für pthread_cond_(timed)wait: “Wenn ein Signal geliefert wird … setzt der Thread das Warten auf die Bedingungsvariable fort, als ob sie nicht unterbrochen worden wäre, oder er soll aufgrund eines falschen Aufwachens Null zurückgeben.” Andere Sperrfunktionen zeigen an EINTR Bei Unterbrechung durch ein Signal (z read) oder müssen fortgesetzt werden (z pthread_mutex_lock). Wenn es also keine anderen Gründe für ein falsches Aufwachen gäbe, pthread_cond_wait hätte wie eine von diesen definiert werden können.

    – Steve Jessop

    21. Dezember 2011 um 19:09 Uhr


  • Ein verwandter Artikel auf Wikipedia: Falsches Aufwachen

    – Paläc

    7. Januar 2015 um 22:51 Uhr

  • Nützlich Vladimir Prus: Falsches Aufwachen.

    – iammilind

    3. August 2015 um 11:24 Uhr

  • Viele Funktionen können ihre Aufgabe nicht vollständig erfüllen (unterbrochene E/A) und beobachtende Funktionen können Nicht-Ereignisse empfangen, wie z. B. eine Änderung an einem Verzeichnis, in dem die Änderung abgebrochen oder rückgängig gemacht wurde. Was ist das Problem?

    – Neugieriger

    22. Juni 2019 um 21:09 Uhr

Benutzeravatar von acm
acm

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

  • Ein Thread ist blockiert pthread_cond_wait kann von dem Anruf zurückkehren, obwohl kein Anruf erfolgt pthread_call_signal oder pthread_cond_broadcast auf die Bedingung aufgetreten.
  • Ein Thread ist blockiert pthread_cond_wait kehrt wegen eines Anrufs zurück pthread_cond_signal oder pthread_cond_broadcastjedoch wird nach dem erneuten Abrufen des Mutex festgestellt, dass das zugrunde liegende Prädikat nicht mehr wahr ist.

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

  • 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 tut, mit dem Element, das er auf einer CPU erworben hat.
  • Thread 2 versucht, ein Element aus der Warteschlange zu entfernen, stellt jedoch fest, dass die Warteschlange leer ist, wenn er unter dem Mutex „calls“ überprüft wird pthread_cond_waitund blockiert das Anruferwartungssignal/Rundsendung.
  • Thread 3 erhält den Mutex, fügt ein neues Element in die Warteschlange ein, benachrichtigt die Bedingungsvariable und gibt die Sperre frei.
  • Als Antwort auf die Benachrichtigung von Thread 3 wird die Ausführung von Thread 2, der auf die Bedingung gewartet hat, geplant.
  • Bevor es Thread 2 jedoch gelingt, auf die CPU zu gelangen und die Warteschlangensperre zu übernehmen, 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 fährt damit fort, das Element, das Thread 3 eingefügt hat, aus der Warteschlange zu nehmen, gibt die Sperre frei und macht alles, was es mit dem Element tut, das Thread 3 in die Warteschlange eingereiht hat.
  • Thread 2 kommt jetzt auf eine CPU und erhält die Sperre, aber wenn er das Prädikat überprüft, stellt er fest, dass die Warteschlange leer ist. Thread 1 hat den Gegenstand “gestohlen”, sodass das Aufwachen falsch zu sein scheint. Thread 2 muss erneut auf die Bedingung warten.

Da Sie also das Prädikat bereits immer unter einer Schleife überprüfen müssen, macht es keinen Unterschied, ob die zugrunde liegenden Bedingungsvariablen andere Arten von falschen Aufwecken haben können.

  • Jawohl. Im Wesentlichen geschieht dies, wenn ein Ereignis anstelle eines Synchronisierungsmechanismus mit einer Zählung verwendet wird. Leider scheint es, dass POSIX-Semaphore (zumindest unter Linux) auch spurius Wakeups unterliegen. Ich finde es nur etwas seltsam, dass ein grundlegender Funktionsausfall von Synchronisationsprimitiven einfach als “normal” akzeptiert wird und auf Benutzerebene umgangen werden muss 🙁 Vermutlich würden Entwickler wütend werden, wenn ein Systemaufruf dokumentiert würde mit einem Abschnitt „Spurious segfault“ oder vielleicht „Falsches Verbinden mit der falschen URL“ oder „Falsches Öffnen der falschen Datei“.

    – Martin Jakob

    22. Dezember 2011 um 11:07 Uhr

  • Das häufigere Szenario eines „falschen Aufwachens“ ist höchstwahrscheinlich der Nebeneffekt eines Aufrufs von pthread_cond_broadcast(). Nehmen wir an, Sie haben einen Pool von 5 Threads, zwei wachen mit der Übertragung 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 einfach erneut, sieht einen ungültigen Zustand und geht wieder in den Ruhezustand. In beiden Fällen löst das Überprüfen des Prädikats das Problem. IMO sollten Benutzer im Allgemeinen keine rohen POSIX-Mutexe und -Bedingungen verwenden.

    – CubicleSoft

    23. Mai 2016 um 9:40 Uhr

  • @MartinJames – Wie wäre es mit dem klassischen “falschen” EINTR? Ich stimme zu, dass das ständige Testen auf EINTR in einer Schleife ein bisschen nervig ist und den Code ziemlich hässlich macht, aber Entwickler tun es trotzdem, um zufällige Brüche zu vermeiden.

    – CubicleSoft

    23. Mai 2016 um 9:42 Uhr

  • @Yola Nein, das kann es nicht, weil du einen Mutex um das sperren sollst pthread_cond_signal/broadcast und Sie können dies nicht tun, bis der Mutex durch Aufrufen entsperrt wird pthread_cond_wait.

    – a3f

    13. Dezember 2016 um 19:51 Uhr

  • Das Beispiel dieser Antwort ist sehr realistisch und ich stimme zu, dass das Überprüfen von Prädikaten eine gute Idee ist. Könnte es jedoch nicht ebenso gut behoben werden, indem der problematische Schritt „Thread 1 beendet seine aktuelle Aufgabe und kehrt für weitere Arbeit in die Warteschlange zurück“ und ersetzt wird durch „Thread 1 beendet seine aktuelle Aufgabe und geht zurück zum Warten auf die Bedingungsvariable”? Das würde den in der Antwort beschriebenen Fehlermodus beseitigen, und ich bin mir ziemlich sicher, dass der Code dadurch korrekt wird. in Abwesenheit von falschen Wakeups. Gibt es eine tatsächliche Implementierung, die in der Praxis falsche Wakeups erzeugt?

    – Quuxplusone

    4. Juli 2017 um 1:20 Uhr

Benutzeravatar von NPE
NPE

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

Scheinbare Aufweckvorgänge mögen seltsam klingen, aber auf einigen Multiprozessorsystemen kann das vollständig vorhersagbare Zustandsaufwecken alle Zustandsvariablenoperationen erheblich verlangsamen.

Im Folgenden comp.programming.threads Diskussionerweitert er den Gedanken hinter dem Design:

Patrick Doyle wrote: 
> In article , Tom Payne   wrote: 
> >Kaz Kylheku  wrote: 
> >: It is so because implementations can sometimes not avoid inserting 
> >: these spurious wakeups; it might be costly to prevent them. 

> >But why?  Why is this so difficult?  For example, are we talking about 
> >situations where a wait times out just as a signal arrives? 

> You know, I wonder if the designers of pthreads used logic like this: 
> users of condition variables have to check the condition on exit anyway, 
> so we will not be placing any additional burden on them if we allow 
> spurious wakeups; and since it is conceivable that allowing spurious 
> wakeups could make an implementation faster, it can only help if we 
> allow them. 

> They may not have had any particular implementation in mind. 

You're actually not far off at all, except you didn't push it far enough. 

The intent was to force correct/robust code by requiring predicate loops. This was 
driven by the provably correct academic contingent among the "core threadies" in 
the working group, though I don't think anyone really disagreed with the intent 
once they understood what it meant. 

We followed that intent with several levels of justification. The first was that 
"religiously" using a loop protects the application against its own imperfect 
coding practices. The second was that it wasn't difficult to abstractly imagine 
machines and implementation code that could exploit this requirement to improve 
the performance of average condition wait operations through optimizing the 
synchronization mechanisms. 
/------------------[ [email protected] ]------------------\ 
| Compaq Computer Corporation              POSIX Thread Architect | 
|     My book: http://www.awl.com/cseng/titles/0-201-63392-2/     | 
\-----[ http://home.earthlink.net/~anneart/family/dave.html ]-----/ 

  • im Grunde sagt das nichts. Hier wird keine Erklärung gegeben, außer dem anfänglichen Gedanken, dass “es die Dinge vielleicht schneller machen kann”, aber niemand weiß, wie oder ob es überhaupt funktioniert.

    – Bogdan Ionitza

    10. Januar 2019 um 12:26 Uhr

Abschnitt “Mehrfaches Erwachen durch Bedingungssignal” in pthread_cond_signal hat eine Beispielimplementierung von pthread_cond_wait und pthread_cond_signal, die falsche Wakekups beinhaltet.

  • Ich denke, diese Antwort ist falsch, soweit es geht. Die Beispielimplementierung auf dieser Seite hat eine Implementierung von „Einen benachrichtigen“, was äquivalent zu „Alle benachrichtigen“ ist; aber es scheint nicht wirklich zu erzeugen falsch Aufwachen. Die einzige Möglichkeit für einen Thread, aufzuwachen, besteht darin, dass ein anderer Thread “alle benachrichtigen” aufruft, oder dass ein anderer Thread das Ding mit der Aufschrift “einen benachrichtigen”, das wirklich “alle benachrichtigen” aufruft.

    – Quuxplusone

    4. Juli 2017 um 1:16 Uhr

Während ich glaube, dass dies zum Zeitpunkt des Designs nicht berücksichtigt wurde, ist hier ein tatsächlicher technischer Grund: In Kombination mit der Thread-Aufhebung gibt es Bedingungen, unter denen es absolut notwendig sein kann, die Option zum “falschen” Aufwecken zu wählen, zumindest wenn Sie es nicht tun sind bereit, sehr, sehr starke Beschränkungen auferlegen, welche Art von Implementierungsstrategien möglich sind.

Das Hauptproblem besteht darin, dass ein Thread auf eine Stornierung reagiert, während er blockiert ist pthread_cond_wait, müssen die Nebenwirkungen so sein, als ob es kein Signal auf der Bedingungsvariablen verbraucht hätte. Es ist jedoch schwierig (und sehr einschränkend), sicherzustellen, dass Sie nicht bereits ein Signal verbraucht haben, wenn Sie mit dem Abbrechen beginnen, und in diesem Stadium ist es möglicherweise unmöglich, das Signal erneut an die Bedingungsvariable zu senden, da dies möglicherweise der Fall ist in einer Situation sein, in der der Anrufer von pthread_cond_signal ist bereits berechtigt, die condvar zerstört und den Speicher befreit zu haben, in dem sie sich befand.

Die Toleranz für falsches Aufwachen gibt Ihnen ein leichtes Aus. Anstatt weiterhin auf eine Stornierung zu reagieren, wenn sie ankommt, während sie auf einer Bedingungsvariablen blockiert ist, können Sie, wenn Sie möglicherweise bereits ein Signal verbraucht haben (oder wenn Sie faul sein wollen, egal was passiert), stattdessen ein falsches Aufwecken deklarieren. und kehre mit Erfolg zurück. Dies stört den Abbruchvorgang überhaupt nicht, da ein korrekter Aufrufer beim nächsten Schleifen und Aufrufen einfach auf den anstehenden Abbruch reagiert pthread_cond_wait wieder.

1424900cookie-checkWarum hat pthread_cond_wait falsche Wakeups?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy