Паралельність Java: фіксація зворотного відліку проти циклічного бар'єру


160

Я читав API java.util.concurrent і виявив це

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

Мені обидва здаються рівними, але я впевнений, що в ньому є набагато більше.

Наприклад, в CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

Чи є якась інша різниця між ними?
Що таке, use casesде хтось хотів би відновити значення відліку?


12
Засувки - це очікування подій; бар'єри для очікування інших потоків. - Java Concurrency in Practice, B.Goetz та ін.
користувач2418306

Відповіді:


137

Одна з головних відмінностей полягає в тому, що CyclicBarrier приймає (необов'язково) завдання, яке виконується після виконання загальної умови бар'єру.

Це також дозволяє отримати кількість клієнтів, які чекають біля бар'єру, і кількість, необхідну для запуску бар'єру. Після запуску бар'єр скидається і його можна використовувати знову.

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


6
"Це також дозволяє отримати кількість клієнтів, які чекають на бар'єрі, і кількість, необхідне для запуску бар'єру. Після запуску бар'єр скидається і його можна використовувати знову." Мені дуже подобається цей момент. Кілька прочитаних нами статей припускають, що CyclicBarrier є циклічним, оскільки ви викликаєте метод reset (). Це правда, але те, що вони часто не згадують, - це те, що бар'єр скидається автоматично, як тільки він спрацьовує. Я опублікую деякий зразок коду, щоб проілюструвати це.
Кевін Лі

@Kevin Lee Дякую за те, що "бар'єр скидається автоматично, як тільки він спрацьовує". тому не потрібно викликати reset () у коді.
супернова

134

Є ще одна різниця.

Під час використання a CyclicBarrier, припущення полягає в тому, що ви вказуєте кількість ниток очікування, які викликають бар'єр. Якщо ви вказали 5, ви повинні мати принаймні 5 потоків для виклику await().

Використовуючи a CountDownLatch, ви вказуєте кількість дзвінків до countDown()цього, що призведе до звільнення всіх потоків очікування. Це означає, що ви можете використовувати CountDownLatchлише один потік.

"Чому б ти це зробив?", Можеш сказати ти. Уявіть, що ви використовуєте таємничий API, кодований кимось іншим, який виконує зворотні дзвінки. Ви хочете, щоб один з ваших потоків зачекав, поки певний зворотний виклик буде викликано кілька разів. Ви не маєте поняття, в які потоки буде викликано зворотний дзвінок. У цьому випадку CountDownLatchце ідеально, тоді як я не можу придумати жодного способу реалізувати це за допомогою CyclicBarrier(насправді, я можу, але це передбачає тайм-аути ... юк!).

Я просто хочу, щоб це CountDownLatchможна було скинути!


10
Я думаю, що це відповідь, що краще показує теоретичні відмінності. Справа в тому, що засувки можна розірвати, просто зателефонувавши кілька разів методом, тоді як бар'єри потребують точної кількості потоків, щоб зачекати ().
flagg19

43
Правильно - ось основна різниця: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Іван Ворошилін

1
Я погоджуюся, що це було б чудово для того, CountDownLatchщоб перезавантажитись - вирішення, яке я використовую для впровадження грубого очікування сповіщення, полягає лише в тому, щоб оновлюватись CountDownLatchнегайно, коли введений захищений код коду (коли засувка досягне нуля). Це не застосовується за будь-яких обставин / областей, звичайно, але я вважав, що варто відзначити, що це варіант у ситуаціях із золотом.
Ефемера

2
Одна з найкращих відповідей на цю тему. Java Concurrency in Practice- Те ж саме говорить: Latches are for waiting for events; barriers are for waiting for other threads.. Основний і важливий момент, щоб зрозуміти різницю між цими двома.
Рахул Дев Мішра

Java 8 doc каже: "CountDownLatch, ініціалізований до N, може бути використаний для того, щоб один потік зачекав, поки N потоків не виконали якусь дію або якась дія була завершена N разів". мені здається: CountDownLatch -> NUMBEROFCALLS Або CountDownLatch -> NumberOfThreads
NIR

41

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

CyclicBarrier використовує модель розриву "повний або ні" для невдалої спроби синхронізації: Якщо потік передчасно залишає бар'єрну точку через переривання, збій або час очікування, всі інші потоки, що чекають у цій бар'єрній точці, також будуть аномально залишатись через BrokenBarrierException (або InterruptException якщо вони теж були перервані приблизно в один і той же час).


22

Я думаю, що JavaDoc чітко пояснив відмінності. Більшість людей знає, що CountDownLatch неможливо скинути, однак CyclicBarrier може. Але це не єдина різниця, інакше CyclicBarrier може бути перейменований на ResetbleCountDownLatch. Ми повинні сказати відмінності з точки зору їх цілей, які описані в JavaDoc

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

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

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

У CyclicBarrier є лише один тип ниток, вони чекають один одного, вони рівні.


1
"У CyclicBarrier є лише один тип потоків" ... Вони рівні за своєю "роллю очікування", поки інші потоки викликають .await (), але вони можуть бути "не рівними в тому, що вони роблять". Крім того, всі вони повинні бути абсолютно різними екземплярами потоків (!) Одного типу або різних типів, тоді як у CountDownLatch той самий потік може викликати countDown () та впливати на результат.
Володимир Набоков

Я погоджуюся, що CountDownLatch властиво вимагає двох ролей: одного клієнта для countDown та одного клієнта для очікування. З іншого боку, клієнти CyclicBarrier можуть добре працювати з методом очікування.
isaolmez

14

Основна відмінність задокументована прямо у Javadocs для CountdownLatch. А саме:

CountDownLatch ініціалізується із заданим числом. Блокують методи очікування, поки поточна кількість не досягне нуля через виклики методу countDown (), після чого всі потоки очікування звільняються та будь-які наступні виклики негайно повертаються. Це явище однократне - підрахунок не може бути скинутий. Якщо вам потрібна версія, яка скидає кількість, подумайте про використання CyclicBarrier.

джерело 1.6 Javadoc


4
Якщо їх різницю просто можна скинути чи ні, CyclicBarrier може бути краще названий ResetableCountDownLatch, що має більш важливе значення через різницю.
Джеймс.Xu

12

CountDownLatch використовується для одноразової синхронізації. Під час використання CountDownLatch будь-який потік може викликати countDown () стільки разів, скільки їм потрібно. Нитки, які викликаються await (), блокуються, поки кількість не досягне нуля через виклики countDown () іншими розблокованими потоками. Javadoc для CountDownLatch станів:

Блокують методи очікування, поки поточна кількість не досягне нуля через виклики методу countDown (), після чого всі потоки очікування звільняються та будь-які наступні виклики негайно повертаються. ...

Ще одне типове використання - поділити проблему на N частин, описати кожну частину за допомогою Runnable, який виконує цю частину і відлічує на засувку, а чергує всі Runnables до виконавця. Коли всі підрозділи будуть завершені, координаційна нитка зможе пройти через очікування. (Коли нитки повинні неодноразово відраховуватися таким чином, замість цього використовуйте CyclicBarrier.)

Навпаки, циклічний бар'єр використовується для декількох точок синхронізації, наприклад, якщо набір потоків виконує обчислення циклу / фази і потребує синхронізації перед початком наступної ітерації / фази. Відповідно до javadoc для CyclicBarrier :

Бар'єр називається циклічним, оскільки його можна повторно використовувати після звільнення ниток очікування.

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


12

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

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

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierCycles {

    static CyclicBarrier barrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3); 

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

}


class Worker extends Thread {
    @Override
    public void run() {
        try {
            CyclicBarrierCycles.barrier.await();
            System.out.println("Let's play.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

8

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

class MeetingAtendee implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendee(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + " i joined the meeting ...");
        myMeetingQuorumBarrier.await();
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("Meeting canceled! every body dance <by chic band!>");
    }
 }
}

працівник приєднується до наради, чекає, коли інші приїдуть розпочати зустріч. також він виходить, якщо зустріч скасовується :), то ми маємо БОС, як дози не люблять чекати, коли з’являться інші, і якщо він втратить пацієнта, він скасовує зустріч.

class MeetingAtendeeTheBoss implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendeeTheBoss(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + "I am THE BOSS - i joined the meeting ...");
        //boss dose not like to wait too much!! he/she waits for 2 seconds and we END the meeting
        myMeetingQuorumBarrier.await(1,TimeUnit.SECONDS);
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("what WHO canceled The meeting");
    } catch (TimeoutException e) {
        System.out.println("These employees waste my time!!");
    }
 }
}

У звичайний день працівники приходять на зустріч, чекають, коли з’являться інші, і якщо деякі відвідувачі не приходять, їм доведеться чекати нескінченно! в якусь особливу зустріч приходить бос, і він не любить чекати. (5 людей потрібно починати зустріч, але приходить тільки начальник, а також захоплений працівник), тож він скасовує зустріч (сердито)

CyclicBarrier meetingAtendeeQuorum = new CyclicBarrier(5);
Thread atendeeThread = new Thread(new MeetingAtendee(meetingAtendeeQuorum));
Thread atendeeThreadBoss = new Thread(new MeetingAtendeeTheBoss(meetingAtendeeQuorum));
    atendeeThread.start();
    atendeeThreadBoss.start();

Вихід:

//Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// These employees waste my time!!
// Meeting canceled! every body dance <by chic band!>

Існує ще один сценарій, коли інший сторонній потік (землетрус) скасовує зустріч (метод скидання виклику). у цьому випадку всі нитки очікування прокидаються за винятком.

class NaturalDisasters implements Runnable {

CyclicBarrier someStupidMeetingAtendeeQuorum;

public NaturalDisasters(CyclicBarrier someStupidMeetingAtendeeQuorum) {
    this.someStupidMeetingAtendeeQuorum = someStupidMeetingAtendeeQuorum;
}

void earthQuakeHappening(){
    System.out.println("earth quaking.....");
    someStupidMeetingAtendeeQuorum.reset();
}

@Override
public void run() {
    earthQuakeHappening();
 }
}

запущений код призведе до смішного виводу:

// Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// earth quaking.....
// what WHO canceled The meeting
// Meeting canceled! every body dance <by chic band!>

Ви також можете додати секретаря до кімнати засідань, якщо відбудеться зустріч, вона буде документувати кожну річ, але вона не є частиною наради:

class MeetingSecretary implements Runnable {

@Override
public void run() {
        System.out.println("preparing meeting documents");
        System.out.println("taking notes ...");
 }
}

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

public class Visitor implements Runnable{

CountDownLatch exhibitonDoorlatch = null;

public Visitor (CountDownLatch latch) {
    exhibitonDoorlatch  = latch;
}

public void run() {
    try {
        exhibitonDoorlatch .await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("customer visiting exebition");
 }
}

А працівники, як готують виставку:

class Worker implements Runnable {

CountDownLatch myTodoItem = null;

public Worker(CountDownLatch latch) {
    this.myTodoItem = latch;
}

public void run() {
        System.out.println("doing my part of job ...");
        System.out.println("My work is done! remove it from todo list");
        myTodoItem.countDown();
 }
}

    CountDownLatch preperationTodoList = new CountDownLatch(3);

    // exhibition preparation workers  
    Worker      electricalWorker      = new Worker(preperationTodoList);
    Worker      paintingWorker      = new Worker(preperationTodoList);

    // Exhibition Visitors 
    ExhibitionVisitor exhibitionVisitorA = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorB = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorC = new ExhibitionVisitor(preperationTodoList);

    new Thread(electricalWorker).start();
    new Thread(paintingWorker).start();

    new Thread(exhibitionVisitorA).start();
    new Thread(exhibitionVisitorB).start();
    new Thread(exhibitionVisitorC).start();

7

Коротше кажучи , просто зрозуміти ключові функціональні відмінності між цими двома:

public class CountDownLatch {
    private Object mutex = new Object();
    private int count;

    public CountDownLatch(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            while (count > 0) {
                mutex.wait();
            }
        }
    }

    public void countDown() {
        synchronized (mutex) {
            if (--count == 0)
                mutex.notifyAll();
        }

    }
}

і

public class CyclicBarrier {
    private Object mutex = new Object();
    private int count;

    public CyclicBarrier(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            count--;
            while(count > 0)
                mutex.wait();
            mutex.notifyAll();
        }
    }
}

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

Наведені вище класи, однак, є повністю функціональними та еквівалентними в межах наданої функціональності своїм іменам кореспондентів.

Інша примітка CountDownLatch- підкласи внутрішнього класу AQSпід час CyclicBarrierкористування ReentrantLock(я підозрюю, що це може бути навпаки, або обидва можуть використовувати AQS або обидва використовують Lock - без втрати ефективності роботи)


5

Одне очевидною різницею є, що лише N потоків може чекати на CyclicBarrier з N, щоб випуститись за один цикл. Але необмежена кількість потоків може очікувати на CountDownLatch з N. Зменшення відліку може бути виконано однією ниткою N разів або N потоками один раз кожну або їх комбінаціями.


4

У випадку CyclicBarrier, як тільки ВСІ дочірні нитки починають дзвонити barrier.await (), Runnable виконується в Barrier. Barrier.await в кожній дочірній нитці потребуватиме різної тривалості часу, і всі вони закінчуються одночасно.


4

У CountDownLatch основні потоки чекають, коли інші потоки завершать їх виконання. У CyclicBarrier робочі потоки чекають один одного, щоб завершити їх виконання.

Ви не можете повторно використовувати той самий екземпляр CountDownLatch, коли кількість досягне нуля, а засувка відкрита, з іншого боку, CyclicBarrier можна повторно використовувати, скинувши бар'єр, Після того як бар'єр буде зламаний.


Це не повинно бути головною ниткою. Це може бути будь-який потік, який створює CountDownLatch і ділиться ним з іншими не основними потоками.
Анікет Такур

1

CountDownLatch - це відлік будь-чого; CyclicBarrier - це відлік лише для потоку

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

Для CountDownLatch лічильник може бути на робочих або предметах

Для CyclicBarrier лічильник може лише на працівників

Якщо працівник засинає нескінченним сном, з CountDownLatch на предметах, вантажовідправник може відправити; Однак із CyclicBarrier Вантажовідправник ніколи не може бути викликаний


0

@Kevin Lee та @Jon я спробував CyclicBarrier з необов'язковим запуском. Схоже, він працює на початку та після того, як CyclicBarrier підкачується. Ось код і вихід

статичний бар'єр CyclicBarrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("I run in the beginning and after the CyclicBarrier is tipped");
            }
        });

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

Вихідні дані

I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Barrier automatically resets.
I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.