Accadono spurie in Java?


208

Vedendo varie domande relative al blocco e (quasi) sempre trovando il termine "loop a causa di risvegli spuri" 1 Mi chiedo, qualcuno ha sperimentato un tale tipo di risveglio (supponendo ad esempio un ambiente hardware / software decente)?

So che il termine "spurio" non significa una ragione apparente, ma quali possono essere le ragioni di questo tipo di evento?

( 1 Nota: non sto mettendo in discussione la pratica del looping.)

Modifica: una domanda di aiuto (per coloro a cui piacciono gli esempi di codice):

Se ho il seguente programma e lo eseguo:

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

Cosa posso fare per svegliarlo awaitin modo spurio senza aspettare per sempre un evento casuale?


1
Per le JVM che girano su sistemi POSIX e usano pthread_cond_wait()la vera domanda è "Perché pthread_cond_wait ha risvegli spuri?" .
Flusso

Risposte:


204

L' articolo di Wikipedia sui risvegli spuri ha questo compito:

La pthread_cond_wait()funzione in Linux è implementata usando la futexchiamata di sistema. Ogni chiamata di sistema bloccante su Linux ritorna bruscamente con EINTRquando il processo riceve un segnale. ... pthread_cond_wait()non è possibile riavviare l'attesa perché potrebbe mancare un vero risveglio nel poco tempo in cui era fuori dalla futexchiamata di sistema. Questa condizione di competizione può essere evitata solo dal chiamante che controlla un invariante. Un segnale POSIX genererà quindi un risveglio spuria.

Riepilogo : se viene segnalato un processo Linux, i thread di attesa godranno ciascuno di un risveglio spurio piacevole e caldo .

Lo compro. È una pillola più facile da ingoiare rispetto al motivo generalmente vago "è per prestazioni" spesso indicato.


13
Spiegazione migliore qui: stackoverflow.com/questions/1461913/…
Gili,

3
Questo sblocco EINTR è vero per tutte le chiamate di sistema bloccanti nei sistemi derivati ​​Unix. Ciò ha reso il kernel molto più semplice, ma i programmatori delle applicazioni hanno acquistato il peso.
Tim Williscroft,

2
Pensavo che pthread_cond_wait () e gli amici non potessero restituire EINTR, ma restituire zero se svegliati in modo spurio? Da: pubs.opengroup.org/onlinepubs/7908799/xsh/… "Queste funzioni non restituiranno un codice di errore di [EINTR]."
paffuto

2
@jgubby Esatto. La futex()chiamata sottostante ritorna EINTR, ma quel valore di ritorno non viene portato al livello successivo. Il chiamante pthread deve quindi verificare la presenza di un invariante. Quello che stanno dicendo è che quando pthread_cond_wait()ritorni devi controllare di nuovo la tua condizione di loop (invariante), perché l'attesa potrebbe essere stata svegliata in modo spurio. La ricezione di un segnale durante una chiamata di sistema è una possibile causa, ma non è l'unica.
John Kugelman,

1
Presumibilmente, la pthreadbiblioteca potrebbe fornire il proprio invariante e la propria logica di controllo in modo da eliminare spurie, piuttosto che passare tale responsabilità all'utente. Ciò avrebbe (presumibilmente) l'impatto sulla performance dichiarato.

22

Ho un sistema di produzione che mostra questo comportamento. Un thread attende un segnale che è presente un messaggio nella coda. Nei periodi di maggiore affluenza, fino al 20% dei risvegli sono falsi (ovvero quando si sveglia non c'è nulla in coda). Questo thread è l'unico consumatore dei messaggi. Funziona su una scatola con 8 processori Linux SLES-10 ed è costruito con GCC 4.1.2. I messaggi provengono da una fonte esterna e vengono elaborati in modo asincrono perché ci sono problemi se il mio sistema non li legge abbastanza velocemente.


15

Per rispondere alla domanda nel titolo: Sì! succede. Sebbene l' articolo di Wiki menzioni molto di risvegli spuri, una bella spiegazione per lo stesso che mi sono imbattuto è la seguente:

Pensaci ... come qualsiasi altro codice, lo scheduler dei thread potrebbe subire un blackout temporaneo a causa di qualcosa di anormale nell'hardware / software sottostante. Naturalmente, bisogna fare attenzione affinché ciò accada il più raro possibile, ma dal momento che non esiste un software robusto al 100%, è ragionevole supporre che ciò accada e prendersi cura del grazioso recupero nel caso in cui lo scheduler lo rilevi (ad es. osservando i battiti del cuore mancanti).

Ora, come potrebbe recuperare lo scheduler, tenendo conto del fatto che durante il blackout potrebbero mancare alcuni segnali destinati a notificare i thread in attesa? Se lo scheduler non fa nulla, i thread "sfortunati" citati si bloccheranno, aspettando per sempre - per evitare ciò, lo scheduler invierebbe semplicemente un segnale a tutti i thread in attesa.

Ciò rende necessario stabilire un "contratto" per cui il thread in attesa può essere notificato senza motivo. Per essere precisi, ci sarebbe un motivo - blackout dello scheduler - ma poiché il thread è progettato (per una buona ragione) per essere ignaro dei dettagli dell'implementazione interna dello scheduler, è probabile che questo motivo sia meglio presentarlo come "spurio".

Stavo leggendo questa risposta da Source e l'ho trovata abbastanza ragionevole. Leggi anche

Risvegli spuri in Java e come evitarli .

PS: Il link sopra è al mio blog personale che ha ulteriori dettagli sui risvegli spuri.


9

Cameron Purdy ha scritto un post sul blog qualche tempo fa a proposito di essere stato colpito da un falso problema di sveglia. Quindi sì, succede

Immagino sia nelle specifiche (come possibilità) a causa dei limiti di alcune piattaforme su cui Java viene distribuito? anche se potrei sbagliarmi!


Ho letto il post e mi è venuta l'idea di avere unit test per testare la conformità di un'applicazione al paradigma del loop-wait svegliandolo in modo casuale / deterministico. O è già disponibile da qualche parte?
Akarnokd,

È un'altra domanda su SO: "Esiste una VM rigorosa che può essere utilizzata per i test?". Mi piacerebbe vederne uno con memoria thread-local rigorosa - non credo che esistano ancora
oxbow_lakes,

8

Solo per aggiungere questo. Sì, succede e ho trascorso tre giorni a cercare la causa di un problema multi-threading su una macchina a 24 core (JDK 6). 4 di 10 esecuzioni hanno sperimentato questo senza alcun modello. Questo non è mai accaduto su 2 core o 8 core.

Ho studiato del materiale online e questo non è un problema Java ma un comportamento generale raro ma previsto.


Ciao ReneS, stai sviluppando l'app in esecuzione lì? Ha (ha) avuto il metodo wait () che chiama mentre controlla ciclicamente la condizione esterna come è suggerito in java doc docs.oracle.com/javase/6/docs/api/java/lang/… ?
Gumkins,

Ne ho scritto e sì, la soluzione è un ciclo while con un controllo delle condizioni. Il mio errore è stato il ciclo mancante ... ma così ho imparato a conoscere questi risvegli ... mai su due core, spesso su 24cores blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS

Ho avuto esperienze simili quando ho eseguito un'applicazione su un server unix 40+ core. Aveva un'estrema quantità di risvegli spuri. - Quindi, sembra che la quantità di riattivazioni spurie sia direttamente proporzionale alla quantità di core del processore del sistema.
bvdb,

0

https://stackoverflow.com/a/1461956/14731 contiene un'eccellente spiegazione del motivo per cui è necessario proteggersi da risvegli spuri anche se il sistema operativo sottostante non li attiva. È interessante notare che questa spiegazione si applica a più linguaggi di programmazione, incluso Java.


0

Rispondere alla domanda del PO

Cosa posso fare per svegliare questa attesa spuria senza aspettare per sempre un evento casuale?

, nessun risveglio spurio potrebbe svegliare questo thread in attesa!

Indipendentemente dal fatto che wakeups spuri possono o non possono accadere su una particolare piattaforma, in un caso di del PO snippet è positivamente impossibile per Condition.await()tornare e vedere la linea "Wakeup spurie!" nel flusso di output.

A meno che non si stia utilizzando una libreria di classi Java molto esotica

Questo perché di serie, OpenJDK 's ReentrantLockmetodo s' newCondition()restituisce la AbstractQueuedSynchronizer's implementazione di Conditioninterfaccia, annidato ConditionObject(a proposito, è l'unica implementazione di Conditionun'interfaccia in questa libreria di classi), e la ConditionObject' s metodo await()si verifica se la condizione non trattiene e nessun risveglio spurio potrebbe costringere questo metodo a restituire erroneamente.

A proposito, potresti controllarlo da solo poiché è abbastanza facile emulare il risveglio spurio una volta AbstractQueuedSynchronizercoinvolta l' implementazione basata su. AbstractQueuedSynchronizerutilizza basso livello LockSupport's parke unparkmetodi, e se si richiama LockSupport.unparka un filo in attesa su Condition, questa azione non può essere distinto da un risveglio spuria.

Rifattorizzare leggermente lo snippet del PO,

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

e non importa quanto il thread unparking (principale) provi a risvegliare il thread in attesa, Condition.await()in questo caso il metodo non ritornerà mai.

I risvegli spuri sui Conditionmetodi in attesa sono discussi nel javadoc Conditiondell'interfaccia . Anche se lo dice,

quando si attende una Condizione, si può verificare un risveglio spurio

e quello

si consiglia ai programmatori di applicazioni di assumere sempre che possano verificarsi e quindi attendere sempre in un ciclo.

ma in seguito lo aggiunge

Un'implementazione è gratuita per rimuovere la possibilità di risvegli spuri

e AbstractQueuedSynchronizerl'implementazione Conditiondell'interfaccia fa esattamente questo - rimuove qualsiasi possibilità di risvegli spuri .

Ciò vale sicuramente per ConditionObjecti metodi in attesa di altri .

Quindi, la conclusione è:

dovremmo sempre chiamare Condition.awaitnel loop e verificare se la condizione non è valida, ma con OpenJDK standard, la libreria di classi Java non può mai accadere . A meno che, ancora una volta, usi una libreria di classi Java molto insolita (che deve essere molto insolita, perché un'altra libreria di classi Java non OpenJDK nota, attualmente quasi estinta GNU Classpath e Apache Harmony , sembra avere identica implementazione standard Conditiondell'interfaccia)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.