web-dev-qa-db-fra.com

Méthode onSpinWait () de la classe Thread

En apprenant Java 9, je suis tombé sur une nouvelle méthode de classe Thread, appelée onSpinWait​ . Selon javadocs, cette méthode est utilisée pour cela:

Indique que l'appelant est momentanément incapable de progresser, jusqu'à l'occurrence d'une ou plusieurs actions de la part d'autres activités.

Quelqu'un peut-il m'aider à comprendre cette méthode en donnant un exemple concret?

29
Joker

C'est la même chose (et se compile probablement) que l'opcode x86 PAUSE et l'équivalent de la macro Win32 YieldProcessor, la fonction GCC __mm_pause() et la méthode C # Thread.SpinWait

C'est une forme de rendement très affaiblie: elle indique à votre processeur que vous êtes dans une boucle qui peut graver de nombreux cycles de processeur en attendant que quelque chose se produise (attente occupée).

De cette façon, le CPU peut affecter plus de ressources à d'autres threads, sans réellement charger le planificateur du système d'exploitation et retirer un thread prêt à exécuter (ce qui peut être coûteux).

une utilisation courante pour cela est le verrouillage par rotation, lorsque vous savez que la contention sur une mémoire partagée est très rare ou se termine très rapidement, un verrou tournant peut mieux fonctionner qu'un verrou ordinaire.

Un pseudo-code peut ressembler à ceci:

int state = 0; //1 - locked, 0 - unlocked

routine lock:
    while state.cas(new_value=1, wanted_value=0) == false //if state is 0 (unlocked), store 1 (locked) and return true, otherwise just return false.
       yield

routine unlock:
    atomic_store(state,0)

yield peut être implémenté avec Thread.onSpinWait(), laissant entendre qu'en essayant de verrouiller le verrou, le CPU peut donner plus de ressources à d'autres threads.

Cette technique de rendement est extrêmement courante et populaire lors de la mise en œuvre d'un algorithme sans verrouillage, car la plupart d'entre eux dépendent de l'attente occupée (qui est implémentée presque toujours comme une boucle atomique de comparaison et d'échange). cela a toutes les utilisations du monde réel que vous pouvez imaginer.

20
David Haim

Système pur allusion!

Lecture cet article Je cite:

Objectifs

Définissez une API qui permettrait à Java d'indiquer au système d'exécution qu'il se trouve dans une boucle de rotation. L'API sera une pure indication et ne comportera aucune exigence de comportement sémantique (pour exemple, un no-op est une implémentation valide.) Permettre à la JVM de bénéficier de comportements spécifiques à la boucle de rotation qui peuvent être utiles sur certaines plates-formes matérielles. Fournir à la fois une implémentation no-op et une implémentation intrinsèque dans le JDK, et démontrer une exécution bénéficier d'au moins une plate-forme matérielle majeure.

Il y a plusieurs fois un thread doit être suspendu jusqu'à ce que quelque chose en dehors de sa portée change. Une (une) pratique courante était le modèle wait() notify() où un thread attend que l'autre thread les réveille.

Il y a une grande limitation à cela, à savoir que l'autre thread doit être conscient qu'il peut y avoir des threads en attente et doit notifier. Si le travail de l'autre thread est hors de votre contrôle, il n'y a aucun moyen d'être averti.

La seule façon serait un spin-wait. disons que vous avez un programme qui vérifie les nouveaux e-mails et informe l'utilisateur:

while(true) {
    while(!newEmailArrived()) {
    }
    makeNotification();
}

Ce morceau de code s'exécutera des millions de fois par seconde; tourner encore et encore, en utilisant précieux de l'électricité et de la puissance du processeur. Une façon courante de le faire serait d'attendre quelques secondes à chaque itération.

while(true) {
    while(!newEmailArrived()) {
        try {
            Thread.sleep(5000);
        } catch(InterruptedException e) {
        }
    }
    makeNotification();
}

Cela fait un très bon travail. Mais dans les cas où vous devez travailler instantanément, un sommeil peut être hors de question.

Java 9 essaie de résoudre ce problème en introduisant cette nouvelle méthode:

while(true) {
    while(!newEmailArrived()) {
        Thread.onSpinWait();
    }
    makeNotification();
}

Cela fonctionnera exactement de la même manière que sans l'appel de méthode, mais le système est libre de réduire la priorité du processus; ralentir le cycle ou réduire l'électricité sur cette boucle lorsque ses ressources sont nécessaires pour d'autres choses plus importantes.

17
Mordechai

Je veux juste ajouter mes 2 cents après avoir lu le document et le code source pour cela. Cette méthode peut déclencher des optimisations et peut-être pas - donc il faut faire attention - vous ne pouvez pas vraiment vous y fier - puisque c'est un hint à la CPU plus que la demande, il n'y a probablement aucune confiance initiale de toute façon ... Ce qui signifie que son code source réel ressemble à ceci:

@HotSpotIntrinsicCandidate
public static void onSpinWait() {}

Ce que cela signifie en fait, c'est que cette méthode est essentiellement un NO-OP jusqu'à ce qu'elle atteigne le c2 compiler dans le JIT, selon this

7
Eugene

Pour un exemple concret, supposons que vous vouliez implémenter la journalisation asynchrone, où les threads qui veulent journaliser quelque chose, ne veulent pas attendre que leur message de journal soit "publié" (disons écrit dans un fichier), tant que cela finit par le faire (parce qu'ils ont un vrai travail à faire.)

Producer(s):
concurrentQueue.Push("Log my message")

Et disons que vous décidez d'avoir un thread consommateur dédié, seul responsable de l'écriture des messages de journalisation dans un fichier:

(Single)Consumer

while (concurrentQueue.isEmpty())
{
    //what should I do?

}
writeToFile(concurrentQueue.popHead());
//loop

La question est de savoir quoi faire à l'intérieur du bloc while? Java n'a pas fourni de solutions idéales: vous pouvez faire un Thread.sleep (), mais pour combien de temps et c'est lourd; ou un Thread.yield (), mais ce n'est pas spécifié, ou vous pouvez utiliser un verrou ou un mutex *, mais c'est souvent trop lourd et ralentit également les producteurs (et entache l'objectif déclaré de la journalisation asynchrone).

Ce que vous voulez vraiment, c'est de dire au runtime: "Je prévois que je n'attendrai pas trop longtemps, mais je voudrais minimiser les frais généraux en attente/les effets négatifs sur les autres threads". C'est là que Thread.onSpinWait () entre en jeu.

Comme indiqué ci-dessus, sur les plates-formes qui le prennent en charge (comme x86), onSpinWait () est incorporé dans une instruction PAUSE, ce qui vous donnera les avantages que vous souhaitez. Donc:

(Single)Consumer

while (concurrentQueue.isEmpty())
{
    Thread.onSpinWait();

}
writeToFile(concurrentQueue.popHead());
//loop

Cela a été montré empiriquement que cela peut améliorer la latence des boucles de style "occupé-en attente".

Je tiens également à préciser qu'il n'est pas seulement utile dans la mise en œuvre de "spin-locks" (bien qu'il soit certainement utile dans une telle circonstance); le code ci-dessus ne nécessite aucun verrou (spin ou autre) d'aucune sorte.

Si vous voulez entrer dans les mauvaises herbes, vous ne pouvez pas faire mieux que spécifications Intel

* Pour plus de clarté, la JVM est incroyablement intelligente en essayant de minimiser le coût des mutex, et utilisera des verrous légers au départ, mais c'est une autre discussion.

6
igaz