Czy faktycznie zdarzają się fałszywe budzenia w Javie?

208

Widząc różne pytania związane z blokowaniem i (prawie) zawsze znajdując „pętlę z powodu fałszywych pobudek” 1 Zastanawiam się, czy ktoś doświadczył takiego rodzaju pobudki (na przykład zakładając przyzwoite środowisko sprzętowe / programowe)?

Wiem, że termin „fałszywy” oznacza brak wyraźnego powodu, ale jakie mogą być przyczyny takiego zdarzenia?

( 1 Uwaga: nie kwestionuję praktyki zapętlania.)

Edycja: pytanie pomocnicze (dla tych, którzy lubią próbki kodu):

Jeśli mam następujący program i uruchamiam go:

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

Co mogę zrobić, aby obudzić to awaitfałszywie, nie czekając wiecznie na przypadkowe wydarzenie?

akarnokd
źródło
1
W przypadku maszyn JVM działających w systemach POSIX i wykorzystujących pthread_cond_wait()prawdziwe pytanie brzmi: „Dlaczego pthread_cond_wait ma fałszywe budzenie?” .
Flow

Odpowiedzi:

204

Artykuł w Wikipedii na temat fałszywych pobudek ma ten smakołyk:

pthread_cond_wait()Funkcja w systemie Linux jest realizowany za pomocą futexwywołania systemowego. Każde blokujące wywołanie systemowe w systemie Linux nagle wraca, EINTRgdy proces odbierze sygnał. ... pthread_cond_wait()nie może zrestartować oczekiwania, ponieważ może przeoczyć prawdziwe budzenie w krótkim czasie, w którym znajdowało się poza futexwywołaniem systemowym. Tych warunków wyścigu można uniknąć tylko przez osobę sprawdzającą, która sprawdza niezmiennik. Sygnał POSIX wygeneruje zatem fałszywe budzenie.

Podsumowanie : Jeśli zasygnalizowany zostanie proces Linuksa, każdy z oczekujących wątków będzie cieszył się ładnym, gorącym fałszywym wybudzeniem .

Kupuję to. Jest to łatwiejsza pigułka do przełknięcia niż zwykle niejasny powód „z powodu wydajności”.

John Kugelman
źródło
13
Lepsze wyjaśnienie tutaj: stackoverflow.com/questions/1461913/...
Gili
3
Odblokowanie EINTR jest prawdziwe dla wszystkich blokujących wywołań systemowych w systemach pochodnych Unix. To znacznie uprościło jądro, ale programiści aplikacji ponieśli ciężar.
Tim Williscroft,
2
Myślałem, że pthread_cond_wait () i przyjaciele nie mogą zwrócić EINTR, ale zwracają zero, jeśli fałszywie się obudzą? From: pubs.opengroup.org/onlinepubs/7908799/xsh/… „Te funkcje nie zwracają kodu błędu [EINTR].”
gubby
2
@jgubby Zgadza się. Połączenie bazowe futex()zwraca EINTR, ale ta wartość zwracana nie jest przenoszona do następnego poziomu. Dzwoniący pthread musi zatem sprawdzić, czy nie ma niezmiennika. Mówią, że po pthread_cond_wait()powrocie musisz ponownie sprawdzić warunek pętli (niezmiennik), ponieważ oczekiwanie mogło zostać fałszywie przebudzone. Odbieranie sygnału podczas połączenia systemowego jest jedną z możliwych przyczyn, ale nie jedyną.
John Kugelman,
1
Przypuszczalnie pthreadbiblioteka mogłaby dostarczać własny niezmiennik i własną logikę sprawdzania, aby wyeliminować fałszywe pobudki, zamiast przekazywać tę odpowiedzialność użytkownikowi. Miałoby to (prawdopodobnie) deklarowany wpływ na wydajność.
22

Mam system produkcyjny, który wykazuje takie zachowanie. Wątek czeka na sygnał, że w kolejce jest komunikat. W okresach dużego ruchu do 20% pobudek jest fałszywych (tzn. Gdy się budzi, w kolejce nie ma nic). Ten wątek jest jedynym konsumentem wiadomości. Działa na 8-procesorowym systemie Linux SLES-10 i jest zbudowany z GCC 4.1.2. Wiadomości pochodzą z zewnętrznego źródła i są przetwarzane asynchronicznie, ponieważ występują problemy, jeśli mój system nie odczytuje ich wystarczająco szybko.

Mr.Dirty.Birdy
źródło
15

Aby odpowiedzieć na pytanie w tytule - Tak! tak się dzieje. Chociaż artykuł na Wiki wspomina sporo o fałszywych pobudkach, miłe wytłumaczenie tego, z czym się spotkałem, jest następujące:

Pomyśl o tym ... jak w każdym kodzie, harmonogram wątków może doświadczyć chwilowego zaciemnienia z powodu czegoś nienormalnego w podstawowym sprzęcie / oprogramowaniu. Oczywiście należy zadbać o to, aby zdarzyło się to tak rzadko, jak to możliwe, ale ponieważ nie ma czegoś takiego jak w 100% niezawodne oprogramowanie, rozsądnie jest założyć, że tak się dzieje i zadbać o płynne odzyskiwanie w przypadku, gdy harmonogram wykryje to (np. obserwując brakujące bicie serca).

W jaki sposób program planujący mógł się zregenerować, biorąc pod uwagę, że w czasie awarii zasilania może brakować niektórych sygnałów, które mają powiadamiać o oczekujących wątkach? Jeśli program planujący nic nie zrobi, wspomniane wątki „pechowe” po prostu się zawieszą i będą czekać wiecznie - aby tego uniknąć, program po prostu wyśle ​​sygnał do wszystkich oczekujących wątków.

Dlatego konieczne jest zawarcie „kontraktu”, w którym oczekujący wątek może zostać powiadomiony bez podania przyczyny. Mówiąc ściślej, istnieje przyczyna - zaciemnienie programu planującego - ale ponieważ wątek został zaprojektowany (z dobrego powodu) tak, aby był nieświadomy wewnętrznych szczegółów implementacji programu planującego, powód ten prawdopodobnie lepiej przedstawić jako „fałszywy”.

Czytałem tę odpowiedź ze Źródła i uznałem ją za rozsądną. Przeczytaj także

Sztuczne budzenie w Javie i sposoby ich unikania .

PS: Powyższy link prowadzi do mojego osobistego bloga, który zawiera dodatkowe informacje na temat fałszywych pobudek.

Aniket Thakur
źródło
9

Cameron Purdy napisał jakiś wpis na blogu o tym, że został trafiony przez fałszywy problem z pobudką. Tak więc się zdarza

Zgaduję, że jest w specyfikacji (jako możliwość) z powodu ograniczeń niektórych platform, na których Java jest wdrażana? chociaż mogę się mylić!

oxbow_lakes
źródło
Przeczytałem post i podsunąłem mi pomysł na przeprowadzenie testów jednostkowych w celu przetestowania zgodności jednej aplikacji z paradygmatem zapętlenie-czekanie poprzez wybudzenie go losowo / deterministycznie. Czy jest już gdzieś dostępny?
akarnokd
To jest kolejne pytanie dotyczące SO: „Czy istnieje ścisła maszyna wirtualna, której można użyć do testowania?”. Chciałbym zobaczyć taki, który ma ścisłą pamięć lokalną dla wątków - nie sądzę, żeby jeszcze istniał
oxbow_lakes
8

Po prostu to dodaj. Tak, zdarza się i spędziłem trzy dni na poszukiwaniu przyczyny problemu wielowątkowości na maszynie 24-rdzeniowej (JDK 6). 4 na 10 egzekucji doświadczyło tego bez żadnego wzorca. To się nigdy nie zdarzyło na 2 rdzeniach lub 8 rdzeniach.

Studiowałem trochę materiałów online i nie jest to problem związany z Javą, ale ogólne rzadkie, ale oczekiwane zachowanie.

ReneS
źródło
Witaj ReneS, czy rozwijałeś działającą tam aplikację? Czy (czy) ma metodę wait () wywołującą podczas sprawdzania pętli warunek zewnętrzny, jak sugeruje to java doc docs.oracle.com/javase/6/docs/api/java/lang/… ?
Gumkins
Napisałem o tym i tak, rozwiązaniem jest pętla while ze sprawdzaniem stanu. Moim błędem była brakująca pętla ... ale dowiedziałem się o tych pobudzeniach ... nigdy na dwóch rdzeniach, często na 24 rdzeniach blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS
Miałem podobne doświadczenia, kiedy uruchomiłem aplikację na ponad 40-rdzeniowym serwerze unix. Miał ekstremalną liczbę fałszywych pobudek. - Wygląda więc na to, że ilość fałszywych pobudek jest wprost proporcjonalna do liczby rdzeni procesora w systemie.
bvdb
0

https://stackoverflow.com/a/1461956/14731 zawiera doskonałe wyjaśnienie, dlaczego należy unikać fałszywych pobudek, nawet jeśli podstawowy system operacyjny ich nie wyzwala. Warto zauważyć, że to wyjaśnienie dotyczy wielu języków programowania, w tym Java.

Gili
źródło
0

Odpowiedź na pytanie PO

Co mogę zrobić, aby obudzić to fałszywie oczekiwane, nie czekając wiecznie na przypadkowe wydarzenie?

, żadne fałszywe budzenie nie mogłoby obudzić tego oczekującego wątku!

Niezależnie od tego, czy fałszywych wybudzeń może lub nie może się zdarzyć na danej platformie, w przypadku OP snippet to pozytywnie niemożliwe do Condition.await()powrotu i zobaczyć linię „Spurious wzbudzenia!” w strumieniu wyjściowym.

Chyba że używasz bardzo egzotycznej biblioteki klas Java

To dlatego, że standard, openjdk „s ReentrantLock” s metoda newCondition()zwraca AbstractQueuedSynchronizer„s implementacja Conditioninterfejsu, zagnieżdżone ConditionObject(nawiasem mówiąc, jest to jedyna realizacja Conditioninterfejsu w tej bibliotece klasy), a ConditionObject” s metoda await()sam sprawdza, czy warunek ten nie wstrzymanie i żadne fałszywe wybudzenie nie zmusiłoby tej metody do błędnego powrotu.

Nawiasem mówiąc, możesz to sprawdzić sam, ponieważ bardzo łatwo jest emulować fałszywe wybudzanie po AbstractQueuedSynchronizerzaangażowaniu opartej na implementacji. AbstractQueuedSynchronizerużywa niskiego poziomu LockSupportdydaktycznego parki unparkmetod, a jeśli wywołasz LockSupport.unparkna nitce w oczekiwaniu na Conditiondziałanie to nie można odróżnić od fałszywych wznawianiu.

Nieznacznie refaktoryzuje fragment OP,

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

, i bez względu na to, jak mocno wątek unparking (główny) próbowałby obudzić oczekujący wątek, Condition.await()metoda nigdy nie powróci w tym przypadku.

Fałszywe pobudki Conditionoczekujących metod są omówione w javadoc Conditioninterfejsu . Chociaż tak mówi,

podczas oczekiwania na Warunek, może nastąpić fałszywy pobudka

i to

zaleca się, aby programiści aplikacji zawsze zakładali, że mogą się zdarzyć, i dlatego zawsze czekają w pętli.

ale później to dodaje

Implementacja jest darmowa, aby usunąć możliwość fałszywych pobudek

i AbstractQueuedSynchronizerimplementacja Conditioninterfejsu robi dokładnie to - eliminuje wszelkie możliwości fałszywych pobudek .

Z pewnością dotyczy to innych ConditionObjectoczekujących metod.

Tak więc wniosek jest:

zawsze powinniśmy wywoływać Condition.awaitpętlę i sprawdzać, czy warunek nie jest spełniony, ale przy standardowym OpenJDK biblioteka klas Java nigdy nie może się zdarzyć . Chyba że ponownie użyjesz bardzo nietypowej biblioteki klas Java (która musi być bardzo niezwykła, ponieważ inne dobrze znane biblioteki klas Java innej niż OpenJDK, obecnie prawie wymarłe GNU Classpath i Apache Harmony , wydają się mieć identyczną standardową implementację Conditioninterfejsu)

igor.zh
źródło