Quelles sont les principales utilisations de yield () et en quoi diffère-t-il de join () et d'interruption ()?

106

Je suis un peu confus quant à l'utilisation de la yield()méthode en Java, en particulier dans l'exemple de code ci-dessous. J'ai également lu que yield () est «utilisé pour empêcher l'exécution d'un thread».

Mes questions sont:

  1. Je crois que le code ci-dessous donne le même résultat lors de son utilisation yield()et de son non-utilisation. Est-ce correct?

  2. Quelles sont, en fait, les principales utilisations de yield()?

  3. En quoi yield()diffère-t- join()il des interrupt()méthodes et ?

L'exemple de code:

public class MyRunnable implements Runnable {

   public static void main(String[] args) {
      Thread t = new Thread(new MyRunnable());
      t.start();

      for(int i=0; i<5; i++) {
          System.out.println("Inside main");
      }
   }

   public void run() {
      for(int i=0; i<5; i++) {
          System.out.println("Inside run");
          Thread.yield();
      }
   }
}

J'obtiens le même résultat en utilisant le code ci-dessus avec et sans utiliser yield():

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run
divz
la source
Cette question doit être fermée car elle est trop large .
Raedwald
Non, il ne renvoie pas le même résultat lorsque vous avez yield()et non. lorsque vous avez un grand i plutôt que 5, vous pouvez voir l'effet de la yield()méthode.
lakshman

Réponses:

97

Source: http://www.javamex.com/tutorials/threads/yield.shtml

les fenêtres

Dans l'implémentation Hotspot, le Thread.yield()fonctionnement a changé entre Java 5 et Java 6.

Dans Java 5, Thread.yield()appelle l'appel d'API Windows Sleep(0). Cela a pour effet spécial d' effacer le quantum du thread actuel et de le mettre à la fin de la file d'attente pour son niveau de priorité . En d'autres termes, tous les threads exécutables de la même priorité (et ceux de plus grande priorité) auront une chance de s'exécuter avant que le thread produit ne reçoive le temps CPU. Lorsqu'il sera finalement reprogrammé, il reviendra avec un quantum complet , mais ne «reportera» aucun des quantum restants à partir du moment de la cession. Ce comportement est un peu différent d'un sommeil non nul où le thread endormi perd généralement 1 valeur quantique (en fait, 1/3 d'un tick de 10 ou 15 ms).

Dans Java 6, ce comportement a été modifié. La VM Hotspot implémente désormais Thread.yield()à l'aide de l' SwitchToThread()appel d'API Windows . Cet appel oblige le thread actuel à abandonner sa tranche de temps actuelle , mais pas son quantum entier. Cela signifie qu'en fonction des priorités des autres threads, le thread de production peut être planifié à nouveau dans une période d'interruption plus tard . (Voir la section sur la planification des threads pour plus d'informations sur les tranches de temps.)

Linux

Sous Linux, Hotspot appelle simplement sched_yield(). Les conséquences de cet appel sont un peu différentes, et peut-être plus graves que sous Windows:

  • un thread produit n'obtiendra pas une autre tranche de CPU tant que tous les autres threads n'auront pas eu une tranche de CPU ;
  • (au moins dans le noyau 2.6.8 et les versions ultérieures), le fait que le thread a donné est implicitement pris en compte par l'heuristique du planificateur sur sa récente allocation de CPU - ainsi, implicitement, un thread qui a donné pourrait recevoir plus de CPU lorsqu'il est planifié dans l'avenir.

(Voir la section sur la planification des threads pour plus de détails sur les priorités et les algorithmes de planification.)

Quand l'utiliser yield()?

Je dirais pratiquement jamais . Son comportement n'est pas défini de manière standard et il existe généralement de meilleures façons d'effectuer les tâches que vous voudrez peut-être effectuer avec yield ():

  • si vous essayez d' utiliser uniquement une partie du processeur , vous pouvez le faire de manière plus contrôlable en estimant la quantité de processeur utilisée par le thread dans son dernier morceau de traitement, puis en dormant pendant un certain temps pour compenser: voir les méthode sleep () ;
  • si vous attendez qu'un processus ou une ressource se termine ou devienne disponible, il existe des moyens plus efficaces pour y parvenir, par exemple en utilisant join () pour attendre qu'un autre thread se termine, en utilisant le mécanisme d' attente / notification pour autoriser un thread pour signaler à un autre qu'une tâche est terminée, ou idéalement en utilisant l'une des constructions de concurrence Java 5 telles qu'un sémaphore ou une file d'attente de blocage .
Sathwick
la source
18
"restant quantique", "quantique entier" - quelque part en cours de route, quelqu'un a oublié ce que signifie le mot "quantique"
kbolino
@kbolino Quantum est le nouvel atome.
Evgeni Sergeev
2
@kbolino - ... latin: "autant que", "combien" . Je ne vois pas en quoi cela est en aucune manière contradictoire avec l'utilisation ci-dessus. Le mot signifie simplement une quantité décrite de quelque chose, donc le diviser en portions utilisées et restantes me semble parfaitement raisonnable.
Periata Breatta
@PeriataBreatta Je suppose que cela a plus de sens si vous connaissez le mot en dehors de la physique. La définition physique était la seule que je connaissais.
kbolino
J'ai mis une prime sur cette question pour que cette réponse soit mise à jour pour 7, 8, 9. Modifiez-la avec les informations actuelles sur 7, 8 et 8 et vous obtiendrez la prime.
40

Je vois que la question a été réactivée avec une prime, demandant maintenant quelles en sont les utilisations pratiques yield. Je vais donner un exemple de mon expérience.

Comme nous le savons, yieldforce le thread appelant à abandonner le processeur sur lequel il s'exécute afin qu'un autre thread puisse être programmé pour s'exécuter. Ceci est utile lorsque le thread en cours a terminé son travail pour le moment mais veut revenir rapidement au début de la file d'attente et vérifier si une condition a changé. En quoi est-ce différent d'une variable de condition? dit essentiellement "autoriser un thread différent à fonctionner, mais permettez-moi de me remettre au travail très rapidement car je m'attends à ce que quelque chose change très très rapidement dans mon état". Cela fait allusion à une rotation occupée, où une condition peut changer rapidement mais la suspension du thread entraînerait une perte de performances importante.yieldpermet au thread de revenir beaucoup plus rapidement à un état d'exécution. Lors de l'attente d'une variable de condition, le thread est suspendu et doit attendre qu'un autre thread signale qu'il doit continuer.yield

Mais assez de babillage, voici un exemple concret: le motif parallèle du front d'onde. Un exemple de base de ce problème est le calcul des «îlots» individuels de 1 dans un tableau bidimensionnel rempli de 0 et de 1. Un «îlot» est un groupe de cellules adjacentes les unes aux autres verticalement ou horizontalement:

1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1

Ici, nous avons deux îles de 1: en haut à gauche et en bas à droite.

Une solution simple consiste à effectuer un premier passage sur l'ensemble du tableau et à remplacer les valeurs 1 par un compteur incrémentiel de sorte qu'à la fin, chaque 1 soit remplacé par son numéro de séquence dans l'ordre principal de la ligne:

1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8

Dans l'étape suivante, chaque valeur est remplacée par le minimum entre elle-même et les valeurs de ses voisins:

1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4

Nous pouvons maintenant facilement déterminer que nous avons deux îles.

La partie que nous voulons exécuter en parallèle est l'étape où nous calculons les minimums. Sans entrer trop dans les détails, chaque thread obtient des lignes de manière entrelacée et s'appuie sur les valeurs calculées par le thread traitant la ligne ci-dessus. Ainsi, chaque thread doit être légèrement en retard par rapport au thread traitant la ligne précédente, mais doit également suivre le rythme dans un délai raisonnable. Plus de détails et une mise en œuvre sont présentés par moi-même dans ce document . Notez que l'utilisation de sleep(0)qui est plus ou moins l'équivalent C de yield.

Dans ce cas, yieldon a utilisé pour forcer chaque thread à se mettre en pause, mais comme le thread traitant la ligne adjacente avançait très rapidement entre-temps, une variable de condition s'avérerait un choix désastreux.

Comme vous pouvez le voir, yieldc'est une optimisation assez fine. L'utiliser au mauvais endroit, par exemple attendre une condition qui change rarement, entraînera une utilisation excessive du processeur.

Désolé pour le long babillage, j'espère que je me suis fait comprendre.

Tudor
la source
1
IIUC ce que vous présentez dans le document, l'idée est que dans ce cas, il est plus efficace d'attendre occupé, en appelant yieldlorsque la condition n'est pas satisfaite pour donner aux autres threads la chance de procéder au calcul, plutôt que d'utiliser plus haut- primitives de synchronisation de niveau, non?
Petr Pudlák
3
@ Petr Pudlák: Oui. J'ai comparé cela à l'utilisation de la signalisation de thread et la différence de performance était énorme dans ce cas. Étant donné que la condition peut devenir vraie très rapidement (c'est le problème clé), les variables de condition sont trop lentes car le thread est mis en attente par le système d'exploitation, plutôt que d'abandonner le processeur pendant une très courte durée en utilisant yield.
Tudor
@Tudor excellente explication!
Développeur Marius Žilėnas
1
"Notez l'utilisation de sleep (0) qui est plus ou moins l'équivalent C de yield." .. eh bien, si vous voulez sleep (0) avec java, pourquoi ne l'utiliseriez-vous pas? Thread.sleep () est une chose qui existe déjà. Je ne sais pas si cette réponse fournit un raisonnement sur la raison pour laquelle on utiliserait Thread.yield () au lieu de Thread.sleep (0); Il existe également un fil conducteur expliquant pourquoi ils sont différents.
eis
@eis: Thread.sleep (0) vs Thread.yield () dépasse le cadre de cette réponse. Je ne mentionnais Thread.sleep (0) que pour les personnes à la recherche d'un équivalent proche en C. La question portait sur l'utilisation de Thread.yield ().
Tudor
12

Au sujet des différences entre yield(), interrupt()et join()- en général, pas seulement en Java:

  1. céder : littéralement, «céder» signifie lâcher prise, abandonner, abandonner. Un thread cédant indique au système d'exploitation (ou à la machine virtuelle, ou non) qu'il est prêt à laisser d'autres threads être planifiés à sa place. Cela indique qu'il ne fait pas quelque chose de trop critique. Ce n'est qu'un indice, cependant, et son effet n'est pas garanti.
  2. jonction : lorsque plusieurs threads «rejoignent» sur un handle, un jeton ou une entité, ils attendent tous que tous les autres threads concernés aient terminé l'exécution (entièrement ou jusqu'à leur propre jointure correspondante). Cela signifie qu'un tas de threads ont tous terminé leurs tâches. Ensuite, chacun de ces threads peut être planifié pour continuer d'autres travaux, étant en mesure d'assumer que toutes ces tâches sont effectivement terminées. (À ne pas confondre avec les jointures SQL!)
  3. interruption : Utilisé par un thread pour «pousser» un autre thread qui est en veille, en attente ou en cours de connexion - afin qu'il soit programmé pour continuer à s'exécuter, peut-être avec une indication qu'il a été interrompu. (À ne pas confondre avec les interruptions matérielles!)

Pour Java en particulier, voir

  1. Joindre:

    Comment utiliser Thread.join? (ici sur StackOverflow)

    Quand rejoindre les discussions?

  2. Rendement:

  3. Interrompre:

    Thread.interrupt () est-il maléfique? (ici sur StackOverflow)

einpoklum
la source
Que voulez-vous dire en joignant une poignée ou un jeton? Les méthodes wait () et notify () sont sur Object, permettant à un utilisateur d'attendre sur n'importe quel Object arbitraire. Mais join () semble moins abstrait et doit être appelé sur le Thread spécifique que vous souhaitez terminer avant de continuer ... n'est-ce pas?
spaaarky21
@ spaaarky21: Je voulais dire en général, pas nécessairement en Java. De plus, a wait()n'est pas une jointure, il s'agit d'un verrou sur l'objet que le thread appelant tente d'acquérir - il attend que le verrou soit libéré par d'autres et acquis par le thread. J'ai modifié ma réponse en conséquence.
einpoklum
10

Premièrement, la description réelle est

Provoque une pause temporaire de l'objet thread en cours d'exécution et autorise l'exécution d'autres threads.

Maintenant, il est très probable que votre thread principal exécutera la boucle cinq fois avant que la runméthode du nouveau thread ne soit exécutée, donc tous les appels à yieldne se produiront qu'après l'exécution de la boucle du thread principal.

joinarrêtera le thread en cours jusqu'à ce que le thread appelé avec join()ait fini de s'exécuter.

interruptva interrompre le thread sur lequel il est appelé, provoquant InterruptedException .

yield permet un basculement de contexte vers d'autres threads, de sorte que ce thread ne consommera pas toute l'utilisation du processeur du processus.

MByD
la source
+1. Notez également qu'après l'appel de yield (), il n'y a toujours aucune garantie que le même thread ne sera pas sélectionné à nouveau pour l'exécution, étant donné un pool de threads de priorité égale.
Andrew Fielden
Cependant, l' SwitchToThread()appel est meilleur que Sleep (0) et cela devrait être un bogue en Java :)
Петър Петров
4

Les réponses actuelles sont obsolètes et nécessitent une révision compte tenu des changements récents.

Il n'y a pas de différence pratiqueThread.yield() entre les versions de Java depuis 6 à 9.

TL, DR;

Conclusions basées sur le code source d'OpenJDK ( http://hg.openjdk.java.net/ ).

Si vous ne prenez pas en compte le support HotSpot des sondes USDT (les informations de traçage du système sont décrites dans le guide dtrace ) et la propriété JVM, ConvertYieldToSleeple code source de yield()est presque le même. Voir l'explication ci-dessous.

Java 9 :

Thread.yield()appelle la méthode spécifique au système d'exploitation os::naked_yield():
Sous Linux:

void os::naked_yield() {
    sched_yield();
}

Sous Windows:

void os::naked_yield() {
    SwitchToThread();
}

Java 8 et versions antérieures:

Thread.yield()appelle la méthode spécifique au système d'exploitation os::yield():
Sous Linux:

void os::yield() {
    sched_yield();
}

Sous Windows:

void os::yield() {  os::NakedYield(); }

Comme vous pouvez le voir, Thread.yeald()sous Linux est identique pour toutes les versions de Java.
Voyons Windows à os::NakedYield()partir du JDK 8:

os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    if (os::Kernel32Dll::SwitchToThreadAvailable()) {
        return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep(0);
    }
    return os::YIELD_UNKNOWN ;
}

La différence entre Java 9 et Java 8 dans le contrôle supplémentaire de l'existence de la SwitchToThread()méthode de l'API Win32 . Le même code est présent pour Java 6.
Le code source de os::NakedYield()JDK 7 est légèrement différent mais il a le même comportement:

    os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    // We use GetProcAddress() as ancient Win9X versions of windows doen't support SwitchToThread.
    // In that case we revert to Sleep(0).
    static volatile STTSignature stt = (STTSignature) 1 ;

    if (stt == ((STTSignature) 1)) {
        stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
        // It's OK if threads race during initialization as the operation above is idempotent.
    }
    if (stt != NULL) {
        return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep (0) ;
    }
    return os::YIELD_UNKNOWN ;
}

La vérification supplémentaire a été abandonnée car la SwitchToThread()méthode est disponible depuis Windows XP et Windows Server 2003 (voir les notes msdn ).

Gregory.K
la source
2

Quelles sont, en fait, les principales utilisations de yield ()?

Yield suggère au CPU que vous pouvez arrêter le thread actuel et commencer à exécuter des threads avec une priorité plus élevée. En d'autres termes, attribuer une valeur de priorité faible au thread actuel pour laisser de la place pour des threads plus critiques.

Je crois que le code ci-dessous donne le même résultat à la fois lors de l'utilisation de yield () et lors de ne pas l'utiliser. Est-ce correct?

NON, les deux produiront des résultats différents. Sans yield (), une fois que le thread obtient le contrôle, il exécutera la boucle 'Inside run' en une seule fois. Cependant, avec un yield (), une fois que le thread obtient le contrôle, il imprimera une fois le 'Inside run', puis passera le contrôle à un autre thread le cas échéant. Si aucun thread n'est en attente, ce thread sera repris. Ainsi, chaque fois que "Inside run" est exécuté, il recherchera d'autres threads à exécuter et si aucun thread n'est disponible, le thread actuel continuera à s'exécuter.

En quoi yield () est-il différent des méthodes join () et interrupt ()?

yield () est pour donner de la place à d'autres threads importants, join () est pour attendre qu'un autre thread termine son exécution, et interrupt () est pour interrompre un thread en cours d'exécution pour faire autre chose.

Abbas
la source
Je voulais juste confirmer si cette affirmation était vraie Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one go? Précisez s'il vous plaît.
Abdullah Khan
0

Thread.yield()fait passer le thread de l'état "Running" à l'état "Runnable". Remarque: cela ne fait pas passer le thread à l'état "En attente".

Prashant Gunjal
la source
@PJMeisch, Il n'y a pas d' RUNNINGétat pour les java.lang.Threadinstances. Mais cela n'empêche pas un état "d'exécution" natif pour le thread natif pour lequel une Threadinstance est un proxy.
Solomon Slow
-1

Rendement de filetage ()

Lorsque nous invoquons la méthode Thread.yield (), le planificateur de thread maintient le thread en cours d'exécution à l'état Runnable et choisit un autre thread de priorité égale ou supérieure. S'il n'y a pas de thread de priorité égale et supérieure, il replanifie le thread appelant yield (). Souvenez-vous que la méthode yield ne fait pas passer le thread à l'état Attente ou Bloqué. Il peut uniquement faire passer un thread de l'état d'exécution à l'état exécutable.

joindre()

Lorsque la jointure est appelée par une instance de thread, ce thread indiquera au thread en cours d'exécution d'attendre que le thread de jonction se termine. Join est utilisé dans les situations où une tâche qui doit être terminée avant la fin de la tâche en cours.

Prashant Kumar
la source
-4

La fonction principale de yield () est de mettre une application multi-thread en attente.

toutes ces différences de méthodes sont yield () met le thread en attente lors de l'exécution d'un autre thread et revenant après la fin de ce thread, join () réunira le début des threads s'exécutant jusqu'à la fin et d'un autre thread à exécuter après que ce thread ait terminé, interromp () arrêtera l'exécution d'un thread pendant un certain temps.

K.Hughley
la source
Merci pour votre réponse. Cependant, il ne fait que répéter ce que les autres réponses décrivent déjà en détail. J'offre la prime pour les cas d'utilisation appropriés où yielddevraient être utilisés.
Petr Pudlák