CountDownLatch vs. Semaphore


92

C'è qualche vantaggio nell'usare

java.util.concurrent.CountdownLatch

invece di

java.util.concurrent.Semaphore ?

Per quanto ne so, i seguenti frammenti sono quasi equivalenti:

1. Semaforo

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

Tranne che nel caso n. 2 il latch non può essere riutilizzato e, cosa più importante, è necessario sapere in anticipo quanti thread verranno creati (o attendere che vengano tutti avviati prima di creare il latch).

Quindi in quale situazione potrebbe essere preferibile il fermo?

Risposte:


109

Il latch CountDown viene spesso utilizzato per l'esatto opposto del tuo esempio. Generalmente, avresti molti thread che si bloccano su "await ()" che verrebbero avviati contemporaneamente quando il paese raggiunge lo zero.

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!

È inoltre possibile utilizzarlo come "barriera" in stile MPI che fa sì che tutti i thread attendono che altri thread raggiungano un certo punto prima di procedere.

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

Detto questo, il latch CountDown può essere tranquillamente utilizzato nel modo in cui hai mostrato nel tuo esempio.


1
Grazie. Quindi i miei due esempi non sarebbero equivalenti se più thread potessero attendere il latch ... a meno che sem.acquire (num_threads); è seguito da sem.release (num_threads) ;? Penso che li renderebbe di nuovo equivalenti.
finnw

In un certo senso, sì, a patto che ogni thread chiamato acquisisca seguito dal rilascio. A rigor di termini, no. Con un latch, tutti i thread possono essere avviati contemporaneamente. Con il semaforo, diventano idonei uno dopo l'altro (il che potrebbe comportare una pianificazione dei thread diversa).
James Schek

La documentazione Java sembra implicare che un CountdownLatch si adatti bene al suo esempio: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . In particolare, "Un CountDownLatch inizializzato su N può essere utilizzato per fare in modo che un thread attenda fino a quando N thread non hanno completato un'azione o un'azione è stata completata N volte."
Chris Morris

Hai ragione. Aggiornerò un po 'la mia risposta per riflettere che questi sono gli usi più comuni di CountDownLatch che ho visto rispetto al fatto che è l'uso previsto.
James Schek

11
Questo risponde alla domanda Qual è l'uso più frequente di CountDownLatch? Non risponde alla domanda originale riguardante i vantaggi / differenze dell'utilizzo di un CountDownLatch su un semaforo.
Marco Lackovic

67

CountDownLatch viene utilizzato per avviare una serie di thread e quindi attendere fino al completamento di tutti (o fino a quando non chiamano countDown()un determinato numero di volte.

Il semaforo viene utilizzato per controllare il numero di thread simultanei che utilizzano una risorsa. Quella risorsa può essere qualcosa come un file, o potrebbe essere la cpu limitando il numero di thread in esecuzione. Il conteggio su un semaforo può aumentare e diminuire quando thread diversi chiamano acquire()e release().

Nel tuo esempio, stai essenzialmente usando Semaphore come una sorta di Count UP Latch. Dato che il tuo intento è aspettare che tutti i thread finiscano, l'uso di CountdownLatchrende la tua intenzione più chiara.


22

Breve riassunto:

  1. Semaphore e CountDownLatch hanno uno scopo diverso.

  2. Usa Semaphore per controllare l'accesso del thread alla risorsa.

  3. Usa CountDownLatch per attendere il completamento di tutti i thread

Definizione di semaforo da javadocs:

Un semaforo mantiene una serie di permessi. Ogni acquisizione () si blocca se necessario fino a quando non è disponibile un permesso , quindi lo prende. Ogni release () aggiunge un permesso, potenzialmente rilasciando un acquirente che blocca.

Tuttavia, non viene utilizzato alcun oggetto permesso effettivo; il semaforo tiene solo un conteggio del numero disponibile e agisce di conseguenza.

Come funziona ?

I semafori vengono utilizzati per controllare il numero di thread simultanei che utilizzano una risorsa, che può essere qualcosa come un dato condiviso o un blocco di codice ( sezione critica ) o qualsiasi file.

Il conteggio su un semaforo può aumentare e diminuire quando diversi thread chiamano acquire() e release(). Ma in qualsiasi momento, non puoi avere un numero di thread maggiore del conteggio dei semafori.

Casi d'uso del semaforo:

  1. Limitazione dell'accesso simultaneo al disco (questo può ridurre le prestazioni a causa di ricerche su disco concorrenti)
  2. Limitazione della creazione di thread
  3. Pooling / limitazione della connessione JDBC
  4. Limitazione della connessione di rete
  5. Limitazione della CPU o attività ad alta intensità di memoria

Dai un'occhiata a questo articolo per gli usi del semaforo.

Definizione CountDownLatch da javadocs:

Un aiuto per la sincronizzazione che consente a uno o più thread di attendere fino al completamento di una serie di operazioni eseguite in altri thread.

Come funziona?

CountDownLatch funziona avendo un contatore inizializzato con il numero di thread, che viene decrementato ogni volta che un thread completa la sua esecuzione. Quando il conteggio arriva a zero, significa che tutti i thread hanno completato la loro esecuzione e il thread in attesa di latch riprende l'esecuzione.

CountDownLatch Casi d'uso:

  1. Ottenere il massimo parallelismo: a volte si desidera avviare più thread contemporaneamente per ottenere il massimo parallelismo
  2. Attendi il completamento di N thread prima di avviare l'esecuzione
  3. Rilevamento deadlock.

Dai un'occhiata a questo articolo per comprendere chiaramente i concetti di CountDownLatch.

Dai un'occhiata anche a Fork Join Pool in questo articolo . Ha alcune somiglianze con CountDownLatch .


7

Di 'che sei entrato in un negozio di golf professionisti, sperando di trovare un quartetto,

Quando sei in fila per ottenere un tee time da uno degli addetti del pro shop, essenzialmente hai chiamato proshopVendorSemaphore.acquire(), una volta ottenuto un tee time, hai chiamato. proshopVendorSemaphore.release()Nota: uno qualsiasi degli addetti gratuiti può aiutarti, cioè risorsa condivisa.

Ora ti avvicini allo starter, fa partire un CountDownLatch(4)e chiama await()ad aspettare gli altri, da parte tua hai chiamato check-in ie CountDownLatch. countDown()e così fa il resto del quartetto. Quando tutti arrivano, lo starter dà il via (la await()chiamata ritorna)

Ora, dopo nove buche quando ognuno di voi si prende una pausa, ipoteticamente coinvolgiamo di nuovo lo starter, usa un "nuovo" CountDownLatch(4)per iniziare la buca 10, la stessa attesa / sincronizzazione della buca 1.

Tuttavia, se lo starter avesse usato a CyclicBarrierper cominciare, avrebbe potuto resettare la stessa istanza nella Buca 10 invece di un secondo latch, che usa & throw.


1
Non sono sicuro di aver capito la tua risposta, ma se stai tentando di descrivere come funzionano CountdownLatch e Semaphore, questo non è l'oggetto della domanda.
finnw

10
Purtroppo non so nulla di golf.
portatore dell'anello

ma le cose iniziali potrebbero essere fatte altrettanto bene con .acquire (giocatori) e aumentare il conteggio relativo con il rilascio. il countdownlatch sembra avere solo meno funzionalità e nessuna riutilizzabilità.
Lassi Kinnunen

1

Guardando la fonte disponibile gratuitamente, non c'è magia nell'implementazione delle due classi, quindi le loro prestazioni dovrebbero essere più o meno le stesse. Scegli quello che rende più ovvio il tuo intento.


0

CountdownLatchfa attendere i thread sul await()metodo, fino a quando il conteggio non raggiunge lo zero. Quindi forse vuoi che tutti i tuoi thread aspettino fino a 3 invocazioni di qualcosa, quindi tutti i thread possono andare. Un Latchgeneralmente non può essere ripristinato.

A Semaphoreconsente ai thread di recuperare i permessi, il che impedisce a troppi thread di essere eseguiti contemporaneamente, bloccandosi se non riesce a ottenere i permessi necessari per procedere. I permessi possono essere restituiti Semaphoreconsentendo agli altri thread in attesa di procedere.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.