CountDownLatch vs Semaphore

94

Czy jest jakaś korzyść z używania

java.util.concurrent.CountdownLatch

zamiast

java.util.concurrent.Semaphore ?

O ile wiem, poniższe fragmenty są prawie równoważne:

1. Semafor

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

Tyle że w przypadku # 2 zatrzasku nie można ponownie użyć i co ważniejsze, musisz wcześniej wiedzieć, ile wątków zostanie utworzonych (lub poczekaj, aż wszystkie zostaną uruchomione, zanim utworzysz zatrzask).

Więc w jakiej sytuacji może być preferowany zatrzask?

finnw
źródło

Odpowiedzi:

111

CountDownLatchjest często używane jako dokładne przeciwieństwo twojego przykładu. Ogólnie rzecz biorąc, miałbyś wiele wątków blokujących się, await()które byłyby uruchamiane jednocześnie, gdy odliczanie osiągnęło 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!

Możesz również użyć tego jako „bariery” w stylu MPI, która powoduje, że wszystkie wątki czekają, aż inne wątki nadrobią zaległości, zanim przejdziesz dalej.

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

To wszystko powiedziawszy, CountDownLatchmożna bezpiecznie używać w sposób pokazany w swoim przykładzie.

James Schek
źródło
1
Dzięki. Więc moje dwa przykłady nie byłyby równoważne, gdyby wiele wątków mogło czekać na zatrzask ... chyba że sem.acquire (num_threads); następuje sem.release (num_threads) ;? Myślę, że to sprawiłoby, że znów byłyby równoważne.
finnw
W pewnym sensie tak, o ile każdy wątek nazywany nabywaniem, po którym następuje uwolnienie. Ściśle mówiąc, nie. Zatrzask umożliwia jednoczesne uruchamianie wszystkich wątków. Dzięki semaforowi stają się one kwalifikowalne jeden po drugim (co może skutkować różnymi harmonogramami wątków).
James Schek
Dokumentacja Java wydaje się sugerować, że CountdownLatch dobrze pasuje do jego przykładu: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . W szczególności „CountDownLatch zainicjowany na N może służyć do tego, aby jeden wątek czekał, aż N wątków zakończy jakąś akcję lub jakaś akcja zostanie ukończona N razy”.
Chris Morris,
Masz rację. Zaktualizuję trochę moją odpowiedź, aby odzwierciedlić, że są to najczęstsze zastosowania CountDownLatch, które widziałem, w porównaniu z tym, że jest to zamierzone zastosowanie.
James Schek,
11
To odpowiada na pytanie Jakie jest najczęstsze zastosowanie CountDownLatch? Nie odpowiada na pierwotne pytanie dotyczące zalet / różnic używania CountDownLatch nad semaforem.
Marco Lackovic
67

CountDownLatch służy do uruchamiania serii wątków, a następnie czekania, aż wszystkie z nich zostaną ukończone (lub do wywołania countDown()określonej liczby razy.

Semafor służy do kontrolowania liczby współbieżnych wątków używających zasobu. Ten zasób może być czymś w rodzaju pliku lub procesorem, ograniczając liczbę wykonywanych wątków. Licznik semafora może rosnąć i maleć, gdy różne wątki wywołują acquire()i release().

W twoim przykładzie zasadniczo używasz Semaphore jako rodzaju Count UP Latch. Biorąc pod uwagę, że Twoim zamiarem jest zaczekanie na zakończenie wszystkich wątków, użycie funkcji CountdownLatchsprawia, że ​​Twój zamiar jest jaśniejszy.

mtruesdell
źródło
23

Krótkie podsumowanie:

  1. Semaphorei CountDownLatchsłuży różnym celom.

  2. Służy Semaphoredo kontrolowania dostępu wątków do zasobu.

  3. Służy CountDownLatchdo czekania na zakończenie wszystkich wątków

Semaphore definicja z Javadocs:

A Semaphoreutrzymuje zestaw zezwoleń. Każdy acquire()blokuje się, jeśli to konieczne, aż do uzyskania zezwolenia , a następnie je przyjmuje. Każdy release()dodaje zezwolenie, potencjalnie zwalniając blokującego nabywcę.

Jednak żadne rzeczywiste obiekty zezwoleń nie są używane; po Semaphoreprostu liczy dostępną liczbę i działa odpowiednio.

Jak to działa?

Semafory są używane do kontrolowania liczby współbieżnych wątków, które używają zasobu. Może to być coś w rodzaju udostępnionych danych, blok kodu ( sekcja krytyczna ) lub dowolny plik.

Liczba na a Semaphoremoże rosnąć i maleć, gdy różne wątki wywołują acquire()i release(). Ale w dowolnym momencie nie możesz mieć większej liczby wątków niż liczba semaforów.

Semaphore Przypadków użycia:

  1. Ograniczenie równoczesnego dostępu do dysku (może to spowodować utratę wydajności z powodu wyszukiwania konkurencyjnych dysków)
  2. Ograniczenie tworzenia wątków
  3. Buforowanie / ograniczanie połączeń JDBC
  4. Ograniczanie przepustowości połączenia sieciowego
  5. Dławienie zadań intensywnie wykorzystujących procesor lub pamięć

Zapoznaj się z tym artykułem dotyczącym zastosowań semaforów.

CountDownLatch definicja z Javadocs:

Pomoc w synchronizacji, która pozwala jednemu lub większej liczbie wątków czekać na zakończenie zestawu operacji wykonywanych w innych wątkach.

Jak to działa?

CountDownLatchdziała poprzez zainicjowanie licznika liczbą wątków, która jest zmniejszana za każdym razem, gdy wątek kończy wykonywanie. Gdy liczba osiągnie zero, oznacza to, że wszystkie wątki zakończyły wykonywanie, a wątek oczekujący na zatrzask wznawia wykonywanie.

CountDownLatch Przypadków użycia:

  1. Osiągnięcie maksymalnego równoległości: Czasami chcemy rozpocząć kilka wątków w tym samym czasie, aby osiągnąć maksymalną równoległość
  2. Poczekaj na zakończenie N wątków przed rozpoczęciem wykonywania
  3. Wykrywanie zakleszczenia.

Zapoznaj się z tym artykułem, aby dobrze zrozumieć CountDownLatchpojęcia.

Zajrzyj też do Fork Join Pool w tym artykule . Ma pewne podobieństwa do CountDownLatch.

Ravindra babu
źródło
7

Powiedzmy, że wszedłeś do profesjonalnego sklepu golfowego z nadzieją, że znajdziesz czwórkę,

Kiedy stoisz w kolejce, aby uzyskać czas na tee od jednego z pracowników sklepu pro, w zasadzie dzwoniłeś proshopVendorSemaphore.acquire(), a kiedy już miałeś czas na tee, proshopVendorSemaphore.release()dzwoniłeś . Uwaga: każdy z bezpłatnych opiekunów może ci pomóc, tj. Udostępnić zasoby.

Teraz podchodzisz do startera, on uruchamia CountDownLatch(4)i dzwoni, await()aby poczekać na innych, ze swojej strony zadzwoniłeś do check-in tj CountDownLatch. countDown()podobnie jak reszta czwórki. Kiedy wszyscy przybędą, starter daje śmiało ( await()zwrot połączenia)

Teraz, po dziewięciu dołkach, kiedy każdy z was robi sobie przerwę, hipotetycznie włączamy ponownie startera, on używa „nowego” CountDownLatch(4)do wybicia otworu 10, to samo czekanie / synchronizacja jak otwór 1.

Jeśli jednak starter użył a CyclicBarrierna początku, mógłby zresetować tę samą instancję w otworze 10 zamiast drugiego zatrzasku, który używa & rzuć.

Raj Srinivas
źródło
1
Nie jestem pewien, czy rozumiem twoją odpowiedź, ale jeśli próbujesz opisać, jak działają CountdownLatch i Semaphore, nie jest to tematem pytania.
finnw
10
Niestety nic nie wiem o golfie.
okaziciel pierścienia
ale początkowe rzeczy można równie dobrze wykonać za pomocą .acquire (graczy) i zwiększać liczbę uwolnionych z wydaniem. Odliczanie wydaje się mieć mniejszą funkcjonalność i brak możliwości ponownego użycia.
Lassi Kinnunen
1

Patrząc na ogólnodostępne źródło, nie ma magii w implementacji tych dwóch klas, więc ich wydajność powinna być bardzo podobna. Wybierz ten, który sprawia, że ​​Twój zamiar jest bardziej oczywisty.

Tom Hawtin - haczyk
źródło
0

CountdownLatchpowoduje, że wątki czekają na await()metodę, aż licznik osiągnie zero. Więc może chcesz, aby wszystkie twoje wątki czekały do ​​3 wywołań czegoś, a następnie wszystkie wątki mogą zostać przeniesione. LatchGeneralnie nie może być skasowany.

A Semaphorepozwala wątkom na pobieranie zezwoleń, co zapobiega jednoczesnemu wykonywaniu zbyt wielu wątków, blokując, jeśli nie może uzyskać zezwoleń wymaganych do kontynuowania. Zezwolenia mogą zostać zwrócone do, aby Semaphoreumożliwić kontynuowanie innych oczekujących wątków.

Spencer Kormos
źródło