CountDownLatch vs. Semaphore

92

Gibt es einen Vorteil der Verwendung

java.util.concurrent.CountdownLatch

anstatt

java.util.concurrent.Semaphore ?

Soweit ich das beurteilen kann, sind die folgenden Fragmente fast gleichwertig:

1. Semaphor

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

Abgesehen davon, dass in Fall 2 der Latch nicht wiederverwendet werden kann und vor allem müssen Sie im Voraus wissen, wie viele Threads erstellt werden (oder warten Sie, bis alle gestartet sind, bevor Sie den Latch erstellen).

In welcher Situation könnte der Riegel also vorzuziehen sein?

finnw
quelle

Antworten:

109

CountDown Latch wird häufig für das genaue Gegenteil Ihres Beispiels verwendet. Im Allgemeinen blockieren viele Threads auf "await ()", die alle gleichzeitig starten, wenn der Countown Null erreicht.

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!

Sie können dies auch als MPI-artige "Barriere" verwenden, die alle Threads darauf warten lässt, dass andere Threads bis zu einem bestimmten Punkt aufholen, bevor Sie fortfahren.

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

Trotzdem kann der CountDown-Latch sicher so verwendet werden, wie Sie es in Ihrem Beispiel gezeigt haben.

James Schek
quelle
1
Vielen Dank. Meine beiden Beispiele wären also nicht gleichwertig, wenn mehrere Threads auf den Latch warten könnten ... es sei denn, sem.acquire (num_threads); wird von sem.release (num_threads) gefolgt;? Ich denke, das würde sie wieder gleichwertig machen.
Finnw
In gewissem Sinne ja, solange jeder Thread, der als Erwerb bezeichnet wird, gefolgt von Freigabe ist. Genau genommen nein. Mit einem Latch können alle Threads gleichzeitig gestartet werden. Mit dem Semaphor werden sie nacheinander förderfähig (was zu einer anderen Thread-Planung führen kann).
James Schek
Die Java-Dokumentation scheint zu implizieren, dass ein CountdownLatch gut zu seinem Beispiel passt: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . Insbesondere "Ein mit N initialisierter CountDownLatch kann verwendet werden, um einen Thread warten zu lassen, bis N Threads eine Aktion abgeschlossen haben oder eine Aktion N-mal abgeschlossen wurde."
Chris Morris
Du hast recht. Ich werde meine Antwort ein wenig aktualisieren, um zu berücksichtigen, dass dies die häufigsten Verwendungen von CountDownLatch sind, die ich gesehen habe, und dass es sich um die beabsichtigte Verwendung handelt.
James Schek
11
Dies beantwortet die Frage, was CountDownLatch am häufigsten verwendet. Die ursprüngliche Frage zu den Vorteilen / Unterschieden der Verwendung eines CountDownLatch gegenüber einem Semaphor wird nicht beantwortet.
Marco Lackovic
67

CountDownLatch wird verwendet, um eine Reihe von Threads zu starten und dann zu warten, bis alle abgeschlossen sind (oder bis sie countDown()eine bestimmte Anzahl von Malen aufrufen .

Semaphor wird verwendet, um die Anzahl der gleichzeitigen Threads zu steuern, die eine Ressource verwenden. Diese Ressource kann so etwas wie eine Datei sein oder die CPU, indem die Anzahl der ausgeführten Threads begrenzt wird. Die Anzahl auf einem Semaphor kann auf und ab gehen, wenn verschiedene Threads acquire()und aufrufen release().

In Ihrem Beispiel verwenden Sie Semaphore im Wesentlichen als eine Art Count- Up- Latch. Da Sie beabsichtigen, auf die Fertigstellung aller Threads zu warten, CountdownLatchwird Ihre Absicht durch die Verwendung von klarer.

mtruesdell
quelle
22

Kurze Zusammenfassung:

  1. Semaphore und CountDownLatch dienen unterschiedlichen Zwecken.

  2. Verwenden Sie Semaphore , um den Thread-Zugriff auf Ressourcen zu steuern.

  3. Verwenden Sie CountDownLatch , um auf den Abschluss aller Threads zu warten

Semaphordefinition aus Javadocs:

Ein Semaphor verwaltet eine Reihe von Genehmigungen. Jeder Erwerb () blockiert bei Bedarf, bis eine Genehmigung verfügbar ist, und nimmt sie dann entgegen. Mit jeder Version () wird eine Genehmigung hinzugefügt, die möglicherweise einen blockierenden Acquirer freigibt.

Es werden jedoch keine tatsächlichen Genehmigungsobjekte verwendet. Das Semaphor zählt nur die verfügbare Anzahl und handelt entsprechend.

Wie funktioniert es ?

Semaphoren werden verwendet, um die Anzahl der gleichzeitigen Threads zu steuern, die eine Ressource verwenden. Diese Ressource kann so etwas wie freigegebene Daten oder ein Codeblock ( kritischer Abschnitt ) oder eine beliebige Datei sein.

Die Anzahl auf einem Semaphor kann steigen und fallen, wenn verschiedene Threads acquire() und release() aufrufen . Zu jedem Zeitpunkt können Sie jedoch nicht mehr Threads als die Anzahl der Semaphore haben.

Anwendungsfälle für Semaphore:

  1. Einschränken des gleichzeitigen Zugriffs auf die Festplatte (dies kann die Leistung aufgrund konkurrierender Festplattensuchen beeinträchtigen)
  2. Einschränkung der Thread-Erstellung
  3. JDBC-Verbindungspooling / -begrenzung
  4. Drosselung der Netzwerkverbindung
  5. CPU- oder speicherintensive Aufgaben drosseln

Schauen Sie sich diesen Artikel für Semaphor-Anwendungen an.

CountDownLatch- Definition aus Javadocs:

Eine Synchronisationshilfe, mit der ein oder mehrere Threads warten können, bis eine Reihe von Vorgängen in anderen Threads abgeschlossen ist.

Wie funktioniert es?

CountDownLatch funktioniert, indem ein Zähler mit der Anzahl der Threads initialisiert wird, der jedes Mal dekrementiert wird, wenn ein Thread seine Ausführung abschließt. Wenn die Anzahl Null erreicht, bedeutet dies, dass alle Threads ihre Ausführung abgeschlossen haben und der auf den Latch wartende Thread die Ausführung fortsetzt.

CountDownLatch Anwendungsfälle:

  1. Maximale Parallelität erreichen: Manchmal möchten wir mehrere Threads gleichzeitig starten, um maximale Parallelität zu erreichen
  2. Warten Sie, bis N Threads abgeschlossen sind, bevor Sie mit der Ausführung beginnen
  3. Deadlock-Erkennung.

Schauen Sie sich diesen Artikel an, um die CountDownLatch-Konzepte klar zu verstehen.

Schauen Sie sich auch Fork Join Pool in diesem Artikel an . Es hat einige Ähnlichkeiten mit CountDownLatch .

Ravindra Babu
quelle
7

Angenommen, Sie sind in den Golf Pro Shop gegangen und haben gehofft, einen Vierer zu finden.

Wenn Sie in der Schlange stehen, um eine Startzeit von einem der Pro-Shop- proshopVendorSemaphore.acquire()Mitarbeiter zu erhalten, haben Sie im Wesentlichen angerufen . Sobald Sie eine proshopVendorSemaphore.release()Startzeit erhalten haben , haben Sie angerufen. Hinweis: Jeder der kostenlosen Mitarbeiter kann Sie bedienen, dh eine gemeinsam genutzte Ressource.

Jetzt gehst du zum Starter, er startet ein CountDownLatch(4)und ruft await()an, um auf andere zu warten, für deinen Teil hast du eingecheckt, dh CountDownLatch. countDown()und der Rest der Vierer auch. Wenn alle ankommen, gibt der Starter los ( await()Anruf kehrt zurück)

Jetzt, nach neun Löchern, in denen jeder von Ihnen eine Pause einlegt, lässt er hypothetisch wieder den Starter einbeziehen. Er verwendet ein 'neues' CountDownLatch(4), um Loch 10 abzuschlagen, das gleiche Warten / Synchronisieren wie Loch 1.

Wenn der Starter jedoch zunächst a verwendet CyclicBarrierhätte, hätte er dieselbe Instanz in Loch 10 anstelle eines zweiten Latch zurücksetzen können, der & throw verwendet.

Raj Srinivas
quelle
1
Ich bin mir nicht sicher, ob ich Ihre Antwort verstehe, aber wenn Sie versuchen zu beschreiben, wie CountdownLatch und Semaphore funktionieren, ist dies nicht Gegenstand der Frage.
Finnw
10
Leider weiß ich nichts über Golf.
Ringträger
Aber das Starter-Zeug könnte genauso gut mit .acquire (Spielern) gemacht werden und die Anzahl der Relesed mit der Veröffentlichung erhöhen. Der Countdown-Latch scheint nur weniger Funktionalität und keine Wiederverwendbarkeit zu haben.
Lassi Kinnunen
1

Wenn man sich die frei verfügbare Quelle ansieht, ist die Implementierung der beiden Klassen nicht magisch, daher sollte ihre Leistung weitgehend gleich sein. Wählen Sie diejenige, die Ihre Absicht offensichtlicher macht.

Tom Hawtin - Tackline
quelle
0

CountdownLatchLässt Threads auf die await()Methode warten , bis die Anzahl Null erreicht hat. Vielleicht möchten Sie, dass alle Ihre Threads bis zu 3 Aufrufen von etwas warten, dann können alle Threads gehen. A Latchkann generell nicht zurückgesetzt werden.

A Semaphoreermöglicht es Threads, Genehmigungen abzurufen, wodurch verhindert wird, dass zu viele Threads gleichzeitig ausgeführt werden, und blockiert, wenn die zum Fortfahren erforderlichen Genehmigungen nicht abgerufen werden können. Berechtigungen können an a zurückgegeben werden, Semaphoredamit die anderen wartenden Threads fortfahren können.

Spencer Kormos
quelle