Des réveils parasites en Java se produisent-ils réellement?

208

Voir diverses questions liées au verrouillage et (presque) toujours trouver la `` boucle à cause de termes de réveils parasites '' 1 Je me demande, quelqu'un a-t-il connu un tel type de réveil (en supposant un environnement matériel / logiciel décent par exemple)?

Je sais que le terme «faux» ne signifie aucune raison apparente, mais quelles peuvent être les raisons d'un tel type d'événement?

( 1 Remarque: je ne remets pas en question la pratique du bouclage.)

Edit: Une question d'aide (pour ceux qui aiment les échantillons de code):

Si j'ai le programme suivant et que je l'exécute:

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

Que puis-je faire pour réveiller cela awaitde manière fallacieuse sans attendre indéfiniment un événement aléatoire?

akarnokd
la source
1
Pour les JVM qui s'exécutent sur des systèmes POSIX et utilisent pthread_cond_wait()la vraie question est "Pourquoi pthread_cond_wait a-t-il des réveils parasites?" .
Débit du

Réponses:

204

L' article de Wikipedia sur les réveils parasites a ce petit mot :

La pthread_cond_wait()fonction sous Linux est implémentée à l'aide de l' futexappel système. Chaque appel système bloquant sous Linux revient brusquement EINTRlorsque le processus reçoit un signal. ... pthread_cond_wait()ne peut pas redémarrer l'attente car il peut manquer un véritable réveil dans le peu de temps où il était en dehors de l' futexappel système. Cette condition de concurrence critique ne peut être évitée que si l'appelant recherche un invariant. Un signal POSIX va donc générer un réveil parasite.

Résumé : si un processus Linux est signalé, ses threads en attente bénéficieront chacun d'un réveil parasite à chaud .

Je l'achète. C'est une pilule plus facile à avaler que la raison généralement vague «c'est pour la performance» souvent invoquée.

John Kugelman
la source
13
Meilleure explication ici: stackoverflow.com/questions/1461913/…
Gili
3
Ce déblocage EINTR est vrai pour tous les appels système bloquants dans les systèmes dérivés Unix. Cela a rendu le noyau beaucoup plus simple, mais les programmeurs d'applications ont acheté le fardeau.
Tim Williscroft
2
Je pensais que pthread_cond_wait () et les amis ne pouvaient pas retourner EINTR, mais renvoyer zéro s'ils se réveillaient faux? De: pubs.opengroup.org/onlinepubs/7908799/xsh/… "Ces fonctions ne renverront pas un code d'erreur de [EINTR]."
gubby
2
@jgubby C'est vrai. L' futex()appel sous - jacent revient EINTR, mais cette valeur de retour n'est pas propulsée au niveau suivant. L'appelant pthread doit donc rechercher un invariant. Ce qu'ils disent, c'est que lorsque pthread_cond_wait()vous revenez, vous devez vérifier à nouveau l'état de votre boucle (invariant), car l'attente peut avoir été réveillée de manière erronée. La réception d'un signal pendant un appel système est une cause possible, mais ce n'est pas la seule.
John Kugelman
1
Vraisemblablement, la pthreadbibliothèque pourrait fournir son propre invariant et sa propre logique de vérification afin d'éliminer les réveils parasites, plutôt que de transmettre cette responsabilité à l'utilisateur. Cela aurait (vraisemblablement) l'impact revendiqué sur les performances.
22

J'ai un système de production qui présente ce comportement. Un thread attend un signal indiquant qu'il y a un message dans la file d'attente. Dans les périodes de pointe, jusqu'à 20% des réveils sont parasites (c'est-à-dire quand il se réveille, il n'y a rien dans la file d'attente). Ce fil est le seul consommateur des messages. Il fonctionne sur un boîtier Linux SLES-10 à 8 processeurs et est construit avec GCC 4.1.2. Les messages proviennent d'une source externe et sont traités de manière asynchrone car il y a des problèmes si mon système ne les lit pas assez rapidement.

Mr.Dirty.Birdy
la source
15

Répondre à la question du titre - Oui! Bien que l' article du Wiki mentionne beaucoup de choses sur les réveils parasites, une belle explication pour la même chose que j'ai rencontrée est la suivante -

Pensez-y ... comme tout code, le planificateur de threads peut subir une panne temporaire en raison de quelque chose d'anormal qui se produit dans le matériel / logiciel sous-jacent. Bien sûr, il convient de veiller à ce que cela se produise aussi rarement que possible, mais comme il n'existe pas de logiciel 100% robuste, il est raisonnable de supposer que cela peut se produire et de veiller à la récupération gracieuse au cas où le planificateur le détecterait (par exemple en observant les battements cardiaques manquants).

Maintenant, comment le planificateur pourrait-il récupérer, en tenant compte du fait que pendant la panne, il pourrait manquer certains signaux destinés à notifier les threads en attente? Si le programmateur ne fait rien, les threads "malchanceux" mentionnés se bloqueront, attendant pour toujours - pour éviter cela, le programmateur enverrait simplement un signal à tous les threads en attente.

Il est donc nécessaire d'établir un "contrat" ​​selon lequel le thread en attente peut être notifié sans raison. Pour être précis, il y aurait une raison - une panne de l'ordonnanceur - mais comme le thread est conçu (pour une bonne raison) pour ignorer les détails de l'implémentation interne de l'ordonnanceur, il est préférable de présenter cette raison comme "fausse".

Je lisais cette réponse de Source et je l'ai trouvée assez raisonnable. Lisez aussi

Réveils parasites en Java et comment les éviter .

PS: Le lien ci-dessus est vers mon blog personnel qui contient des détails supplémentaires sur les réveils parasites.

Aniket Thakur
la source
9

Cameron Purdy a écrit un billet de blog il y a quelque temps sur le fait d'avoir été frappé par un faux problème de réveil. Alors oui, ça arrive

Je suppose que c'est dans la spécification (comme une possibilité) en raison des limitations de certaines des plates-formes sur lesquelles Java est déployé? bien que je puisse me tromper!

oxbow_lakes
la source
J'ai lu le post et m'a donné une idée d'avoir des tests unitaires pour tester la conformité d'une application au paradigme d'attente en boucle en la réveillant de manière aléatoire / déterministe. Ou est-il déjà disponible quelque part?
akarnokd le
C'est une autre question sur SO: "Existe-t-il une machine virtuelle stricte qui peut être utilisée pour les tests?". J'adorerais en voir un avec une mémoire locale stricte - je ne pense pas qu'ils existent encore
oxbow_lakes
8

Juste pour ajouter ceci. Oui, cela se produit et j'ai passé trois jours à rechercher la cause d'un problème de multi-thread sur une machine à 24 cœurs (JDK 6). 4 exécutions sur 10 ont connu cela sans aucun schéma. Cela ne s'est jamais produit sur 2 cœurs ou 8 cœurs.

A étudié du matériel en ligne et ce n'est pas un problème Java mais un comportement général rare mais attendu.

ReneS
la source
Bonjour ReneS, développez-vous (étiez-vous) l'application en cours d'exécution? La méthode wait () at-elle (était-elle) appelée lors de la vérification de la condition externe comme indiqué dans le doc java doc docs.oracle.com/javase/6/docs/api/java/lang/… ?
gumkins
J'ai écrit à ce sujet et oui, la solution est une boucle while avec une vérification de l'état. Mon erreur était la boucle manquante ... mais j'ai donc appris ces réveils ... jamais sur deux cœurs, souvent sur 24cores blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS
J'ai eu des expériences similaires lorsque j'ai exécuté une application sur un serveur Unix 40+. Il y avait une quantité extrême de réveils parasites. - Il semble donc que la quantité de réveils parasites soit directement proportionnelle à la quantité de cœurs de processeur du système.
bvdb
0

https://stackoverflow.com/a/1461956/14731 contient une excellente explication des raisons pour lesquelles vous devez vous prémunir contre les réveils parasites même si le système d'exploitation sous-jacent ne les déclenche pas. Il est intéressant de noter que cette explication s'applique à plusieurs langages de programmation, y compris Java.

Gili
la source
0

Répondre à la question du PO

Que puis-je faire pour réveiller cette attente faussement sans attendre indéfiniment un événement aléatoire?

, aucun réveil parasite ne pourrait réveiller ce fil en attente!

Indépendamment du fait que des réveils parasites peuvent ou non se produire sur une plate-forme particulière, dans le cas de l'extrait de l'OP, il est absolument impossible pourCondition.await() revenir et de voir la ligne « Spurious réveil! » dans le flux de sortie.

Sauf si vous utilisez une bibliothèque de classes Java très exotique

En effet, la méthode standard d' OpenJDK renvoie l' implémentation d' interface imbriquée (soit dit en passant, c'est la seule implémentation d' interface dans cette bibliothèque de classes) et la méthode elle-même vérifie si la condition ne les prises et aucun réveil parasite ne pourraient forcer cette méthode à revenir par erreur.ReentrantLocknewCondition()AbstractQueuedSynchronizerConditionConditionObjectConditionConditionObjectawait()

Soit dit en passant, vous pouvez le vérifier vous-même car il est assez facile d'émuler un réveil parasite une fois que l' AbstractQueuedSynchronizerimplémentation basée est impliquée. AbstractQueuedSynchronizerutilise à faible niveau LockSupportde parket unparkméthodes, et si vous invoquez LockSupport.unparksur un fil en attente surCondition , cette action ne peut être distingué d'un réveil parasite.

Refactorisant légèrement l'extrait de l'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");
    }
}

, et quelle que soit la force avec laquelle le thread non principal (principal) tentera de réveiller le thread en attente, la Condition.await()méthode ne reviendra jamais dans ce cas.

Les faux réveils sur Conditionles méthodes en attente de sont discutés dans le javadoc de l' Conditioninterface . Bien qu'il le dise,

en attendant une condition, un réveil parasite peut se produire

et cela

il est recommandé que les programmeurs d'applications supposent toujours qu'ils peuvent se produire et donc toujours attendre en boucle.

mais il ajoute plus tard que

Une implémentation est gratuite pour supprimer la possibilité de réveils parasites

et AbstractQueuedSynchronizerl'implémentation de l' Conditioninterface fait exactement cela - supprime toute possibilité de réveils parasites .

Cela vaut certainement pour les autres ConditionObject méthodes en attente.

Alors le conclusion est:

nous devons toujours appeler Condition.awaitdans la boucle et vérifier si la condition ne tient pas, mais avec OpenJDK standard, la bibliothèque de classes Java ne peut jamais se produire . Sauf si, encore une fois, vous utilisez une bibliothèque de classes Java très inhabituelle (qui doit être très inhabituelle, car une autre bibliothèque de classes Java non OpenJDK bien connue, actuellement presque éteinte GNU Classpath et Apache Harmony , semble identique à l'implémentation standard de l' Conditioninterface)

igor.zh
la source