Treten tatsächlich falsche Aufweckvorgänge in Java auf?

208

Sehen verschiedene Verriegelungs Zusammenhang Frage und (fast) immer die ‚Schleife wegen falschen Wakeups‘ zu finden Begriffe 1 I Wunder, hat jemand erlebt eine solche Art eines Wake - up (eine anständige Hardware / Software - Umgebung zum Beispiel angenommen wird )?

Ich weiß, dass der Begriff "falsch" keinen offensichtlichen Grund bedeutet, aber was können die Gründe für eine solche Art von Ereignis sein?

( 1 Hinweis: Ich stelle die Schleifenpraxis nicht in Frage.)

Bearbeiten: Eine Hilfsfrage (für diejenigen, die Codebeispiele mögen):

Wenn ich das folgende Programm habe und es ausführe:

public class Spurious {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition cond = lock.newCondition();
        lock.lock();
        try {
            try {
                cond.await();
                System.out.println("Spurious wakeup!");
            } catch (InterruptedException ex) {
                System.out.println("Just a regular interrupt.");
            }
        } finally {
            lock.unlock();
        }
    }
}

Was kann ich tun, um dies awaitfalsch aufzuwecken , ohne ewig auf ein zufälliges Ereignis zu warten?

akarnokd
quelle
1
Für JVMs, die auf POSIX-Systemen ausgeführt werden und pthread_cond_wait()die eigentliche Frage verwenden, lautet die eigentliche Frage: "Warum hat pthread_cond_wait falsche Aufweckvorgänge?" .
Flow

Antworten:

204

Der Wikipedia- Artikel über falsche Weckrufe hat diesen Leckerbissen:

Die pthread_cond_wait()Funktion unter Linux wird über den futexSystemaufruf implementiert . Jeder blockierende Systemaufruf unter Linux kehrt abrupt zurück, EINTRwenn der Prozess ein Signal empfängt. ... pthread_cond_wait()kann das Warten nicht neu starten, da es in der kurzen Zeit außerhalb des futexSystemaufrufs möglicherweise ein echtes Aufwecken verpasst . Diese Racebedingung kann nur vermieden werden, indem der Anrufer nach einer Invariante sucht. Ein POSIX-Signal erzeugt daher ein falsches Aufwecken.

Zusammenfassung : Wenn ein Linux-Prozess signalisiert wird, genießen seine wartenden Threads jeweils ein schönes, heißes, falsches Aufwecken .

Ich kaufe es. Das ist eine Pille, die leichter zu schlucken ist als der oft vage, oft angegebene Grund für die Leistung.

John Kugelman
quelle
13
Bessere Erklärung hier: stackoverflow.com/questions/1461913/…
Gili
3
Diese EINTR-Entsperrung gilt für alle blockierenden Systemaufrufe in von Unix abgeleiteten Systemen. Dies machte den Kernel viel einfacher, aber die Anwendungsprogrammierer kauften die Last.
Tim Williscroft
2
Ich dachte, pthread_cond_wait () und Freunde könnten EINTR nicht zurückgeben, aber Null zurückgeben, wenn sie falsch geweckt werden? Von: pubs.opengroup.org/onlinepubs/7908799/xsh/… "Diese Funktionen geben keinen Fehlercode von [EINTR] zurück."
Gubby
2
@jgubby Das stimmt. Der zugrunde liegende futex()Aufruf wird zurückgegeben EINTR, aber dieser Rückgabewert wird nicht auf die nächste Ebene übertragen. Der pthread-Aufrufer muss daher nach einer Invariante suchen. Was sie sagen ist, dass Sie bei der pthread_cond_wait()Rückgabe Ihre Schleifenbedingung (unveränderlich) erneut überprüfen müssen, da das Warten möglicherweise falsch geweckt wurde. Das Empfangen eines Signals während eines Systemaufrufs ist eine mögliche Ursache, aber nicht die einzige.
John Kugelman
1
Vermutlich pthreadkönnte die Bibliothek ihre eigene Invariante und ihre eigene Überprüfungslogik bereitstellen, um falsche Aufweckvorgänge zu vermeiden, anstatt diese Verantwortung auf den Benutzer zu übertragen. Dies hätte (vermutlich) die behaupteten Auswirkungen auf die Leistung.
22

Ich habe ein Produktionssystem, das dieses Verhalten zeigt. Ein Thread wartet auf ein Signal, dass sich eine Nachricht in der Warteschlange befindet. In Stoßzeiten sind bis zu 20% der Weckvorgänge falsch (dh wenn sie aufwachen, befindet sich nichts in der Warteschlange). Dieser Thread ist der einzige Verbraucher der Nachrichten. Es läuft auf einer Linux SLES-10 8-Prozessor-Box und basiert auf GCC 4.1.2. Die Nachrichten stammen von einer externen Quelle und werden asynchron verarbeitet, da Probleme auftreten, wenn mein System sie nicht schnell genug liest.

Mr.Dirty.Birdy
quelle
15

Um die Frage im Titel zu beantworten - Ja! Es passiert. Obwohl der Wiki-Artikel viel über falsche Weckrufe erwähnt, ist eine nette Erklärung für dasselbe, auf das ich gestoßen bin, wie folgt:

Denken Sie nur daran ... Wie bei jedem Code kann es beim Thread-Scheduler zu einem vorübergehenden Stromausfall kommen, da in der zugrunde liegenden Hardware / Software etwas Ungewöhnliches passiert. Natürlich sollte darauf geachtet werden, dass dies so selten wie möglich geschieht. Da es jedoch keine 100% robuste Software gibt, ist es vernünftig anzunehmen, dass dies passieren kann, und auf die ordnungsgemäße Wiederherstellung zu achten, falls der Scheduler dies feststellt (z durch Beobachtung fehlender Herzschläge).

Wie kann sich der Scheduler nun erholen, wenn er berücksichtigt, dass beim Blackout einige Signale fehlen können, um wartende Threads zu benachrichtigen? Wenn der Scheduler nichts unternimmt, hängen die erwähnten "unglücklichen" Threads einfach und warten für immer. Um dies zu vermeiden, sendet der Scheduler einfach ein Signal an alle wartenden Threads.

Dies macht es notwendig, einen "Vertrag" abzuschließen, dass wartende Threads ohne Grund benachrichtigt werden können. Um genau zu sein, würde es einen Grund geben - einen Scheduler-Blackout -, aber da der Thread (aus gutem Grund) so konzipiert ist, dass er die internen Implementierungsdetails des Schedulers nicht berücksichtigt, ist es wahrscheinlich besser, diesen Grund als "falsch" darzustellen.

Ich habe diese Antwort von Source gelesen und fand sie vernünftig genug. Lesen Sie auch

Falsche Weckrufe in Java und wie man sie vermeidet .

PS: Der obige Link führt zu meinem persönlichen Blog, der zusätzliche Details zu falschen Weckvorgängen enthält.

Aniket Thakur
quelle
9

Cameron Purdy hat vor einiger Zeit einen Blog-Beitrag darüber geschrieben, wie er von dem falschen Weckproblem getroffen wird. Also ja, es passiert

Ich vermute, es ist in der Spezifikation (als eine Möglichkeit) wegen der Einschränkungen einiger der Plattformen, auf denen Java bereitgestellt wird? obwohl ich mich vielleicht irre!

oxbow_lakes
quelle
Ich las den Beitrag und gab mir eine Idee zu Unit-Tests, um die Konformität einer Anwendung mit dem Loop-Wait-Paradigma zu testen, indem ich sie zufällig / deterministisch aufweckte. Oder ist es schon irgendwo verfügbar?
Akarnokd
Es ist eine andere Frage zu SO: "Gibt es eine strenge VM, die zum Testen verwendet werden kann?". Ich würde gerne einen mit striktem Thread-lokalem Speicher sehen - ich glaube, sie existieren noch nicht
oxbow_lakes
8

Nur um das hinzuzufügen. Ja, es passiert und ich habe drei Tage lang auf einem 24-Kern-Computer (JDK 6) nach der Ursache eines Multithreading-Problems gesucht. 4 von 10 Hinrichtungen erlebten dies ohne Muster. Dies geschah nie auf 2 Kernen oder 8 Kernen.

Studierte Online-Material und dies ist kein Java-Problem, sondern ein allgemein seltenes, aber erwartetes Verhalten.

ReneS
quelle
Hallo ReneS, entwickeln Sie die App, die dort ausgeführt wird? Hat (hat) es eine wait () -Methode, die aufgerufen wird, während die Schleife den externen Zustand überprüft, wie es in Java doc docs.oracle.com/javase/6/docs/api/java/lang/… vorgeschlagen wird ?
Gummis
Ich habe darüber geschrieben und ja, die Lösung ist eine while-Schleife mit einer Bedingungsprüfung. Mein Fehler war die fehlende Schleife ... aber so erfuhr ich von diesen Aufwecken ... nie auf zwei Kernen, oft auf 24cores blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS
Ich hatte ähnliche Erfahrungen gemacht, als ich eine Anwendung auf einem Unix-Server mit mehr als 40 Kernen ausführte. Es gab extrem viele falsche Weckrufe. - Es scheint also, dass die Anzahl der falschen Aufweckvorgänge direkt proportional zur Anzahl der Prozessorkerne des Systems ist.
Bvdb
0

https://stackoverflow.com/a/1461956/14731 enthält eine hervorragende Erklärung, warum Sie sich vor falschen Aufwecken schützen müssen, auch wenn das zugrunde liegende Betriebssystem sie nicht auslöst. Es ist interessant festzustellen, dass diese Erklärung für mehrere Programmiersprachen gilt, einschließlich Java.

Gili
quelle
0

Beantwortung der Frage des OP

Was kann ich tun, um dieses Warten fälschlicherweise aufzuwecken, ohne ewig auf ein zufälliges Ereignis zu warten?

, Keine jede falsche Wakeup könnte diese Erwartung Thread aufwachen!

Unabhängig davon , ob falsche Wakeups kann oder nicht auf einer bestimmten Plattform, in einem Fall , der OPs passieren kann Snippet es positiv ist unmöglich für Condition.await()zurückzukehren und die Linie zu sehen „Unechte Wakeup!“ im Ausgabestream.

Es sei denn, Sie verwenden eine sehr exotische Java-Klassenbibliothek

Dies liegt daran , Standard, OpenJDK ‚s ReentrantLock‘ s Methode newCondition()kehrt die AbstractQueuedSynchronizer‚s Umsetzung der ConditionSchnittstelle, verschachtelt ConditionObject(übrigens ist es die einzige Implementierung der ConditionSchnittstelle in dieser Klassenbibliothek) und die ConditionObject‘ s Methode await()selbst überprüft , ob die Bedingung nicht hält und kein falsches Aufwecken könnte diese Methode zwingen, fälschlicherweise zurückzukehren.

Übrigens können Sie es selbst überprüfen, da es ziemlich einfach ist, ein falsches Aufwecken zu emulieren, sobald die AbstractQueuedSynchronizerbasierte Implementierung beteiligt ist. AbstractQueuedSynchronizerverwendet Low-Level LockSupport‚s parkund unparkMethoden, und wenn Sie invoke LockSupport.unparkauf einem Thread wartet auf Condition, kann diese Aktion nicht von einem falschen Wakeup unterschieden werden.

Das OP-Snippet leicht umgestalten,

public class Spurious {

    private static class AwaitingThread extends Thread {

        @Override
        public void run() {
            Lock lock = new ReentrantLock();
            Condition cond = lock.newCondition();
            lock.lock();
            try {
                try {
                    cond.await();
                    System.out.println("Spurious wakeup!");
                } catch (InterruptedException ex) {
                    System.out.println("Just a regular interrupt.");
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private static final int AMOUNT_OF_SPURIOUS_WAKEUPS = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread awaitingThread = new AwaitingThread();
        awaitingThread.start();
        Thread.sleep(10000);
        for(int i =0 ; i < AMOUNT_OF_SPURIOUS_WAKEUPS; i++)
            LockSupport.unpark(awaitingThread);
        Thread.sleep(10000);
        if (awaitingThread.isAlive())
            System.out.println("Even after " + AMOUNT_OF_SPURIOUS_WAKEUPS + " \"spurious wakeups\" the Condition is stil awaiting");
        else
            System.out.println("You are using very unusual implementation of java.util.concurrent.locks.Condition");
    }
}

Unabhängig davon, wie sehr der Unparking-Thread (Hauptthread) versuchen würde, den wartenden Thread zu aktivieren, wird die Condition.await()Methode in diesem Fall niemals zurückkehren.

Die falschen Aufweckmethoden für Conditiondie erwarteten Methoden werden im Javadoc der ConditionSchnittstelle erläutert . Obwohl es das sagt,

Wenn auf eine Bedingung gewartet wird, kann ein falsches Aufwecken auftreten

und das

Es wird empfohlen, dass Anwendungsprogrammierer immer davon ausgehen, dass sie auftreten können, und daher immer in einer Schleife warten.

aber es fügt das später hinzu

Eine Implementierung ist frei, um die Möglichkeit von falschen Aufwecken auszuschließen

und AbstractQueuedSynchronizerdie Implementierung der ConditionSchnittstelle macht genau das - beseitigt jede Möglichkeit von falschen Aufwecken .

Dies gilt sicherlich auch für ConditionObjectdie erwarteten Methoden anderer .

Die Schlussfolgerung lautet also:

Wir sollten immer Condition.awaitin der Schleife aufrufen und prüfen, ob die Bedingung nicht erfüllt ist, aber mit Standard, OpenJDK, Java Class Library kann dies niemals passieren . Es sei denn, Sie verwenden erneut eine sehr ungewöhnliche Java-Klassenbibliothek (was sehr, sehr ungewöhnlich sein muss, da andere bekannte Nicht-OpenJDK-Java-Klassenbibliotheken, derzeit fast ausgestorbene GNU-Klassenpfade und Apache Harmony , mit der Standardimplementierung der ConditionSchnittstelle identisch zu sein scheinen ).

igor.zh
quelle