CountDownLatch проти Semaphore


92

Чи є якась перевага використання

java.util.concurrent.CountdownLatch

замість

java.util.concurrent.Semaphore ?

Наскільки я можу сказати, наступні фрагменти майже еквівалентні:

1. Семафор

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

За винятком того, що у випадку №2 засувку не можна використовувати повторно, і що більш важливо, потрібно заздалегідь знати, скільки потоків буде створено (або дочекайтеся, поки всі вони почнуться, перш ніж створювати засувку.)

Тож у якій ситуації засувка може бути кращою?

Відповіді:


109

Засувка CountDown часто використовується для прямо протилежного Вашого прикладу. Як правило, у вас буде багато потоків, що блокуються на "await ()", і всі вони запускатимуться одночасно, коли відлік досягне нуля.

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!

Ви також можете використовувати це як "бар'єр" у стилі MPI, який змушує всі потоки чекати, поки інші потоки наздоженуть певний момент, перш ніж продовжувати.

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

З усього сказаного, засувку CountDown можна безпечно використовувати так, як ви показали у вашому прикладі.


1
Дякую. Отже, мої два приклади не були б рівнозначними, якби декілька потоків могли зачекати на засувці ... хіба що sem.acquire (num_threads); слідує sem.release (num_threads) ;? Я думаю, що це зробило б їх знову рівнозначними.
finnw

У певному сенсі так, до тих пір, поки кожен потік, що називається набувати, супроводжується звільненням. Строго кажучи, ні. За допомогою засувки всі потоки можуть запускатися одночасно. За допомогою семафору вони стають придатними один за одним (що може призвести до різного планування потоків).
Джеймс Шек,

З документації Java видно, що CountdownLatch добре підходить до його прикладу: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . Зокрема, "CountDownLatch, ініціалізований N, може бути використаний, щоб змусити один потік зачекати, поки N потоків виконають якусь дію, або якусь дію було виконано N разів."
Chris Morris

Ти правий. Я трохи оновлю свою відповідь, щоб відобразити, що це найпоширеніші способи використання CountDownLatch, які я бачив, проти того, що це цільове використання.
Джеймс Шек,

11
Це відповідає на питання Яке найчастіше використовується CountDownLatch? Він не відповідає на вихідне запитання щодо переваг / відмінностей використання CountDownLatch над семафором.
Marco Lackovic

67

CountDownLatch використовується для запуску серії потоків, а потім чекає, поки всі вони будуть завершені (або поки вони не зателефонують countDown()задану кількість разів.

Семафор використовується для контролю кількості паралельних потоків, які використовують ресурс. Цей ресурс може бути чимось на зразок файлу, а може бути процесором, обмежуючи кількість виконуваних потоків. Відлік на семафорі може зростати і зменшуватися, коли різні потоки викликають acquire()і release().

У вашому прикладі ви по суті використовуєте Semaphore як свого роду засувку Count UP . Враховуючи, що ви маєте намір чекати на завершення всіх потоків, використання методу CountdownLatchробить ваш намір зрозумілішим.


22

Короткий зміст:

  1. Semaphore і CountDownLatch виконують різні цілі.

  2. Використовуйте Semaphore для контролю доступу потоку до ресурсу.

  3. Використовуйте CountDownLatch, щоб дочекатися завершення всіх потоків

Семафор визначення з Javadocs:

Семафор підтримує набір дозволів. Кожне з набору блоків () блокує, якщо це необхідно, до тих пір, поки не з’явиться дозвіл , а потім бере його. Кожен випуск () додає дозвіл, потенційно звільняючи блокуючого набувача.

Однак фактичні об'єкти дозволу не використовуються; Семафор просто продовжує підрахунок числа наявних і діє відповідним чином .

Як це працює ?

Семафори використовуються для контролю кількості паралельних потоків, які використовують ресурс. Цей ресурс може бути чимось на зразок спільних даних або блоком коду ( критичний розділ ) або будь-яким файлом.

Відлік на семафорі може зростати і зменшуватися, коли різні потоки викликають acquire() та release(). Але в будь-який момент часу ви не можете мати більшу кількість потоків, більшу за кількість семафорів.

Приклади використання семафору:

  1. Обмеження одночасного доступу до диска (це може призвести до зниження продуктивності через конкуруючі пошуки диска)
  2. Обмеження створення ниток
  3. Пул / обмеження з'єднання JDBC
  4. Мережеве підключення
  5. Зниження центрального процесора або завдань із великою пам’яттю

Погляньте на цю статтю щодо використання семафору.

Визначення CountDownLatch із javadocs:

Допомога синхронізації, яка дозволяє одному або декільком потокам чекати, поки завершиться набір операцій, що виконуються в інших потоках.

Як це працює?

CountDownLatch працює за допомогою ініціалізації лічильника з кількістю потоків, який зменшується кожного разу, коли потік завершує своє виконання. Коли рахунок доходить до нуля, це означає, що всі потоки завершили своє виконання, і потік, що очікує на засувці, продовжує виконання.

CountDownLatch Варіанти використання:

  1. Досягнення максимального паралелізму: Іноді ми хочемо запустити кілька потоків одночасно, щоб досягти максимального паралелізму
  2. Дочекайтеся завершення N потоків перед початком виконання
  3. Виявлення тупикової ситуації.

Погляньте на цю статтю, щоб чітко зрозуміти концепції CountDownLatch.

Погляньте також на Fork Join Pool у цій статті . Він має деяку схожість із CountDownLatch .


7

Скажімо, ви зайшли в магазин для гольфу, сподіваючись знайти четвірку,

Коли ви стоїте в черзі, щоб отримати час від одного з професіоналів магазину, по суті, ви зателефонували proshopVendorSemaphore.acquire(), як тільки ви отримаєте час від телефону, ви зателефонували proshopVendorSemaphore.release().

Тепер ви підходите до стартера, він запускає a CountDownLatch(4)і закликає await()почекати інших, з вашої сторони ви зателефонували зареєстрованому, тобто CountDownLatch. countDown()так само як і решта четвірки. Коли всі прибудуть, стартер дає продовжити ( await()дзвінок повертається)

Тепер, після дев’яти лунок, коли кожен із вас робить перерву, гіпотетично дозволяє залучити стартер знову, він використовує `` новий '', CountDownLatch(4)щоб відрізати отвір 10, так само, як очікування / синхронізація, як отвір 1.

Однак, якби стартер використовував CyclicBarrierдля початку a , він міг би скинути той самий екземпляр у отворі 10 замість другої засувки, яка використовує & throw.


1
Я не впевнений, що розумію вашу відповідь, але якщо ви намагаєтеся описати, як працюють CountdownLatch та Semaphore, це не є предметом питання.
finnw

10
На жаль, я нічого не знаю про гольф.
носій кільця

але стартові речі можна так само добре зробити з .acquire (гравці) і збільшити кількість випущених з виходом. здається, що відлік часу просто має меншу функціональність і відсутність повторного використання.
Лассі Кіннунен,

1

Дивлячись на вільно доступне джерело, в реалізації двох класів немає магії, тому їх ефективність повинна бути приблизно однаковою. Виберіть той, який робить ваш намір більш очевидним.


0

CountdownLatchзмушує потоки чекати на await()метод до тих пір, поки відлік не досягне нуля. Тож, можливо, ви хочете, щоб усі ваші потоки зачекали до 3 викликів чогось, тоді всі потоки можуть піти. LatchЯк правило , не можуть бути скинуті.

A Semaphoreдозволяє потокам отримувати дозволи, що заважає виконувати занадто багато потоків одночасно, блокуючи, якщо він не може отримати дозволи, необхідні для продовження. Дозволи можна повернути, Semaphoreдозволивши іншим потокам очікування продовжувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.