web-dev-qa-db-fra.com

Quels sont les principaux usages de rendement () et en quoi diffèrent-ils de join () et interrupt ()?

Je suis un peu confus quant à l'utilisation de la méthode yield() en Java, en particulier dans l'exemple de code ci-dessous. J'ai aussi 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 produit le même résultat à la fois lorsque vous utilisez yield() et lorsque vous ne l'utilisez pas. Est-ce correct?

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

  3. En quoi yield() est-elle différente des méthodes join() et interrupt()?

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 la même sortie 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
97
divz

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

Les fenêtres

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

Dans Java 5, Thread.yield() appelle l'appel de l'API Windows Sleep(0). Ceci a pour effet spécial de d'effacer le quantum du thread actuel et le mettre à la fin de la file d'attente pour son niveau de priorité. En d'autres termes, tous les threads exécutables ayant la même priorité (et ceux ayant une priorité plus élevée) auront une chance de s'exécuter avant que le thread cédé ne prenne le prochain temps CPU. Quand il sera finalement reprogrammé, il reviendra avec un total quantum complet , mais ne "reportera" pas le quantum restant du moment de la production. Ce comportement est un peu différent d'un sommeil non nul dans lequel le thread en veille perd généralement une valeur quantique (en fait 1/3 d'un tick de 10 ou 15 ms).

Ce comportement a été modifié dans Java 6). Le hotspot VM implémente désormais Thread.yield() à l'aide de l'API Windows SwitchToThread() Cet appel fait que le thread actuel renonce à son heure actuelle, mais pas à son quantum complet. En fonction des priorités des autres threads, le thread qui cède peut être programmé dans une période d'interruption ultérieure . (Voir la section sur 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 cédé ne recevra une autre tranche de CPU que si tous les autres threads ont eu une tranche de CPU ;
  • (du moins dans les versions 2.6.8 et suivantes du noyau), le fait que le thread ait cédé est implicitement pris en compte par les heuristiques du planificateur pour son allocation de CPU récente; ainsi, implicitement, un thread ayant cédé pourrait recevoir plus de CPU lors de sa planification dans l'avenir.

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

Quand utiliser yield()?

Je dirais pratiquement jamais . Son comportement n'est pas défini de manière standard et il existe généralement de meilleurs moyens d'exécuter les tâches que vous pourriez souhaiter effectuer avec yield ():

  • si vous essayez d'utiliser seulement 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 bloc de traitement, puis en veille pendant un certain temps pour compenser: voir la méthode sleep () ;
  • si vous attendez qu'un processus ou une ressource se termine ou devienne disponible, il existe des moyens plus efficaces de le faire, comme en utilisant join () pour attendre la fin d'un autre thread, en utilisant le mécanisme wait/notify pour permettre à un thread de signaler à un autre qu'une tâche est terminée ou, idéalement, en utilisant l'un des = Java 5 structures de concurrence telles qu'un sémaphore ou file d'attente bloquante .
91
Sathwick

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

Comme nous le savons, yield force le thread appelant à abandonner le processeur sur lequel il est exécuté afin qu'un autre thread puisse être planifié. Ceci est utile lorsque le thread en cours a terminé son travail pour le moment mais souhaite revenir rapidement au début de la file d'attente et vérifier si une condition a été modifiée. En quoi est-ce différent d'une variable de condition? yield permet au thread de revenir beaucoup plus rapidement à un état en cours d'exécution. Lorsque vous attendez une variable de condition, le thread est suspendu et doit attendre qu'un autre thread indique qu'il doit continuer. yield dit fondamentalement "autoriser un autre thread à s'exécuter, mais permettez-moi de retourner au travail très vite car je m'attends à ce que quelque chose change très rapidement dans mon état". Cela laisse à penser que le filage est très occupé, car une condition peut changer rapidement, mais la suspension du fil entraînerait une perte de performances importante.

Mais assez bavardage, voici un exemple concret: le motif parallèle du front d’onde. Une instance 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, verticalement ou horizontalement:

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

Nous avons ici 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 tel que, à la fin, chaque 1 soit remplacé par son numéro de séquence en ordre de ligne majeur:

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

À l'étape suivante, chaque valeur est remplacée par le minimum entre elle 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 qui traite la ligne ci-dessus. Ainsi, chaque thread doit légèrement prendre du retard par rapport au traitement de la ligne précédente, mais doit également suivre dans un délai raisonnable. Plus de détails et une implémentation sont présentés par moi-même dans ce document . Notez l'utilisation de sleep(0), qui est plus ou moins l'équivalent C de yield.

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

Comme vous pouvez le constater, yield est une optimisation plutôt fine. L’utiliser au mauvais endroit, par exemple l'attente d'une condition qui change rarement entraînera une utilisation excessive du processeur.

Désolé pour le long babillage, j'espère avoir été clair.

37
Tudor

À propos 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, se rendre. Un thread qui cède 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 que ce n'est pas faire quelque chose de trop critique. Ce n'est qu'un indice, cependant, et rien ne garantit.
  2. join : Lorsque plusieurs threads 'se joignent' sur une poignée, un jeton ou une entité, tous attendent que tous les autres threads pertinents aient terminé leur 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 programmé pour continuer un autre travail, étant capable de supposer que toutes ces tâches sont effectivement terminées. (À ne pas confondre avec les jointures SQL!)
  3. interruption : Utilisé par un thread pour "piquer" un autre thread en veille, ou en attente ou en cours de connexion - afin qu'il soit planifié 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 spécifiquement, voir

  1. Joindre:

    Comment utiliser Thread.join? (ici sur StackOverflow)

    Quand rejoindre les discussions?

  2. Rendement:

  3. Interrompant:

    Thread.interrupt () est-il mauvais? (ici sur StackOverflow)

12
einpoklum

Tout d'abord, la description actuelle est

Fait en sorte que l'objet de thread en cours d'exécution s'interrompt temporairement et permet à d'autres threads de s'exécuter.

Maintenant, il est très probable que votre thread principal exécute la boucle cinq fois avant l'exécution de la méthode run du nouveau thread. Tous les appels à yield ne se produiront donc qu'après la boucle. dans le thread principal est exécuté.

join arrêtera le thread actuel jusqu'à ce que le thread appelé avec join() soit terminé.

interrupt interrompra le thread sur lequel il est appelé, provoquant InterruptedException .

yield autorise un changement de contexte vers d'autres threads, ce thread ne consommera donc pas toute l'utilisation du processeur par le processeur.

9
MByD

Les réponses actuelles sont obsolètes et doivent être révisées compte tenu des modifications récentes.

Il n'y a pas de pratique différence de Thread.yield() entre Java de 6 à 9.

TL; DR;

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

Si vous ne voulez pas prendre en compte le support HotSpot des sondes USDT (les informations de traçage du système sont décrites dans dtrace guide ) et de la propriété JVM ConvertYieldToSleep, le 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 constater, Thread.yeald() sous Linux est identique pour toutes les versions Java.
Voyons la os::NakedYield() de Windows de 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 la vérification supplémentaire de l'existence de la méthode SwitchToThread() de l'API Win32). Le même code est présent pour Java 6.
Le code source de os::NakedYield() dans 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 en raison de la méthode SwitchToThread() étant disponible depuis Windows XP et Windows Server 2003 (voir notes msn) ).

3
Gregory.K

Quels sont, en fait, les principales utilisations de rendement ()?

Yield suggère au processeur 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é basse au thread actuel pour laisser de la place à des threads plus critiques.

Je crois que le code ci-dessous produit le même résultat lors de l’utilisation de rendement () et lorsqu’il ne l’est pas utilisé. Est-ce correct?

NON, les deux produiront des résultats différents. Sans yield (), une fois que le thread aura le contrôle, il exécutera la boucle 'Inside run' en une fois. Cependant, avec une limite (), une fois que le thread a pris le contrôle, il imprime une fois l'exécution "Inside run" puis passe le contrôle à un autre thread, le cas échéant. Si aucun fil n'est en attente, ce fil sera repris. Ainsi, chaque fois que "Inside run" est exécuté, il recherche d'autres threads à exécuter et si aucun thread n'est disponible, le thread en cours continue de s'exécuter.

De quelle manière le rendement () est-il différent des méthodes join () et interrupt ()?

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

2
abbas

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

0
Prashant Gunjal