CountDownLatch contre Semaphore

92

Y a-t-il un avantage à utiliser

java.util.concurrent.CountdownLatch

au lieu de

java.util.concurrent.Semaphore ?

Pour autant que je sache, les fragments suivants sont presque équivalents:

1. Sémaphore

final Semaphore sem = new Semaphore(0);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        sem.release();
      }
    }
  };
  t.start();
}

sem.acquire(num_threads);

2: CountDownLatch

final CountDownLatch latch = new CountDownLatch(num_threads);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        latch.countDown();
      }
    }
  };
  t.start();
}

latch.await();

Sauf que dans le cas n ° 2, le verrou ne peut pas être réutilisé et, plus important encore, vous devez savoir à l'avance combien de threads seront créés (ou attendre qu'ils soient tous démarrés avant de créer le verrou.)

Dans quelle situation le loquet serait-il préférable?

finnw
la source

Réponses:

109

Le verrou CountDown est fréquemment utilisé pour l'exact opposé de votre exemple. Généralement, vous auriez de nombreux threads bloquant sur "await ()" qui commenceraient tous simultanément lorsque le compte à rebours atteignait zéro.

final CountDownLatch countdown = new CountDownLatch(1);
for (int i = 0; i < 10; ++ i){
   Thread racecar = new Thread() {    
      public void run()    {
         countdown.await(); //all threads waiting
         System.out.println("Vroom!");
      }
   };
   racecar.start();
}
System.out.println("Go");
countdown.countDown();   //all threads start now!

Vous pouvez également l'utiliser comme une "barrière" de style MPI qui oblige tous les threads à attendre que les autres threads rattrapent jusqu'à un certain point avant de continuer.

final CountDownLatch countdown = new CountDownLatch(num_thread);
for (int i = 0; i < num_thread; ++ i){
   Thread t= new Thread() {    
      public void run()    {
         doSomething();
         countdown.countDown();
         System.out.printf("Waiting on %d other threads.",countdown.getCount());
         countdown.await();     //waits until everyone reaches this point
         finish();
      }
   };
   t.start();
}

Cela dit, le loquet CountDown peut être utilisé en toute sécurité de la manière que vous avez montrée dans votre exemple.

James Schek
la source
1
Merci. Donc mes deux exemples ne seraient pas équivalents si plusieurs threads pouvaient attendre sur le verrou ... à moins que sem.acquire (num_threads); est suivi de sem.release (num_threads) ;? Je pense que cela les rendrait à nouveau équivalents.
finnw
Dans un sens, oui, tant que chaque thread appelé acquiert suivi de release. À proprement parler, non. Avec un loquet, tous les threads peuvent démarrer simultanément. Avec le sémaphore, ils deviennent éligibles les uns après les autres (ce qui peut entraîner une planification des threads différente).
James Schek
La documentation Java semble impliquer que CountdownLatch correspond bien à son exemple: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . Plus précisément, «Un CountDownLatch initialisé à N peut être utilisé pour faire attendre un thread jusqu'à ce que N threads aient terminé une action ou qu'une action ait été effectuée N fois».
Chris Morris
Vous avez raison. Je mettrai à jour un peu ma réponse pour refléter que ce sont les utilisations les plus courantes de CountDownLatch que j'ai vues et que c'est l'utilisation prévue.
James Schek
11
Cela répond à la question Quelle est l'utilisation la plus fréquente de CountDownLatch? Il ne répond pas à la question initiale concernant les avantages / différences de l'utilisation d'un CountDownLatch sur un sémaphore.
Marco Lackovic
67

CountDownLatch est utilisé pour démarrer une série de threads, puis attendre qu'ils soient tous terminés (ou jusqu'à ce qu'ils appellentcountDown() un nombre donné de fois.

Le sémaphore est utilisé pour contrôler le nombre de threads simultanés qui utilisent une ressource. Cette ressource peut être quelque chose comme un fichier, ou pourrait être le processeur en limitant le nombre de threads en cours d'exécution. Le décompte d'un sémaphore peut augmenter et diminuer lorsque différents threads appellent acquire()et release().

Dans votre exemple, vous utilisez essentiellement Semaphore comme une sorte de Count UP Latch. Étant donné que votre intention est d'attendre la fin de tous les threads, l'utilisation de CountdownLatchrend votre intention plus claire.

mtruesdell
la source
22

Court résumé:

  1. Semaphore et CountDownLatch ont des objectifs différents.

  2. Utilisez Semaphore pour contrôler l'accès des threads aux ressources.

  3. Utilisez CountDownLatch pour attendre la fin de tous les threads

Définition de sémaphore de javadocs:

Un sémaphore maintient un ensemble de permis. Chaque acquisition () bloque si nécessaire jusqu'à ce qu'un permis soit disponible, puis le prend. Chaque version () ajoute un permis, libérant potentiellement un acquéreur bloquant.

Cependant, aucun objet de permis réel n'est utilisé; le sémaphore tient simplement un compte du nombre disponible et agit en conséquence.

Comment ça marche ?

Les sémaphores sont utilisés pour contrôler le nombre de threads simultanés qui utilisent une ressource.Cette ressource peut être quelque chose comme des données partagées ou un bloc de code ( section critique ) ou n'importe quel fichier.

Le décompte d'un sémaphore peut augmenter et diminuer lorsque différents threads appellent acquire() et release(). Mais à tout moment, vous ne pouvez pas avoir plus de threads supérieur au nombre de sémaphore.

Cas d'utilisation de sémaphore:

  1. Limitation de l'accès simultané au disque (cela peut nuire aux performances en raison de recherches de disques concurrents)
  2. Limitation de la création de threads
  3. Regroupement / limitation des connexions JDBC
  4. Limitation de la connexion réseau
  5. Throttling CPU ou tâches gourmandes en mémoire

Jetez un œil à cet article pour les utilisations de sémaphore.

CountDownLatch définition de javadocs:

Aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations en cours d'exécution dans d'autres threads.

Comment ça marche?

CountDownLatch fonctionne en ayant un compteur initialisé avec le nombre de threads, qui est décrémenté chaque fois qu'un thread termine son exécution. Lorsque le nombre atteint zéro, cela signifie que tous les threads ont terminé leur exécution et que les threads en attente de verrouillage reprennent l'exécution.

CountDownLatch Cas d'utilisation:

  1. Atteindre un parallélisme maximal: Parfois, nous voulons démarrer un certain nombre de threads en même temps pour obtenir un parallélisme maximal
  2. Attendre la fin de N threads avant de démarrer l'exécution
  3. Détection de blocage.

Consultez cet article pour comprendre clairement les concepts de CountDownLatch.

Jetez également un œil à Fork Join Pool dans cet article . Il présente quelques similitudes avec CountDownLatch .

Ravindra babu
la source
7

Disons que vous êtes entré dans la boutique de golf pro, dans l'espoir de trouver un quatuor,

Lorsque vous faites la queue pour obtenir une heure de départ de l'un des préposés de la boutique professionnelle, vous avez essentiellement appelé proshopVendorSemaphore.acquire(), une fois que vous avez obtenu une heure de départ, vous avez appeléproshopVendorSemaphore.release() Remarque: n'importe lequel des agents gratuits peut vous servir, c'est-à-dire une ressource partagée.

Maintenant vous marchez vers le démarreur, il commence un CountDownLatch(4)et appelle await()pour attendre les autres, pour votre part vous avez appelé check-in ie CountDownLatch. countDown()et le reste du quatuor aussi. Quand tout arrive, le démarreur donne le feu (await() appel revient)

Maintenant, après neuf trous lorsque chacun de vous fait une pause, laisse hypothétiquement impliquer à nouveau le démarreur, il utilise un `` nouveau '' CountDownLatch(4)pour jouer au trou 10, même attente / synchronisation que le trou 1.

Cependant, si le starter a utilisé un CyclicBarrierpour commencer, il aurait pu réinitialiser la même instance dans le trou 10 au lieu d'un deuxième verrou, qui utilise & throw.

Raj Srinivas
la source
1
Je ne suis pas sûr de comprendre votre réponse, mais si vous essayez de décrire comment CountdownLatch et Semaphore fonctionnent, ce n'est pas le sujet de la question.
fin
10
Malheureusement, je ne connais rien au golf.
porteur de la bague
mais le truc de départ pourrait tout aussi bien être fait avec .acquire (joueurs) et en augmentant le nombre de relesed avec la sortie. le compte à rebours semble avoir moins de fonctionnalités et aucune réutilisabilité.
Lassi Kinnunen
1

En regardant la source disponible gratuitement, il n'y a pas de magie dans l'implémentation des deux classes, donc leurs performances devraient être sensiblement les mêmes. Choisissez celui qui rend votre intention plus évidente.

Tom Hawtin - Tacle
la source
0

CountdownLatchfait attendre les threads sur la await()méthode, jusqu'à ce que le nombre atteigne zéro. Alors peut-être que vous voulez que tous vos threads attendent jusqu'à 3 invocations de quelque chose, alors tous les threads peuvent partir. Un Latchne peut généralement pas être réinitialisé.

A Semaphorepermet aux threads de récupérer les autorisations, ce qui empêche trop de threads de s'exécuter à la fois, bloquant s'il ne peut pas obtenir les autorisations nécessaires pour continuer. Les Semaphoreautorisations peuvent être retournées à un, ce qui permet aux autres threads en attente de continuer.

Spencer Kormos
la source