CountDownLatch vs. Semaphore


92

Existe alguma vantagem em usar

java.util.concurrent.CountdownLatch

ao invés de

java.util.concurrent.Semaphore ?

Pelo que eu posso dizer, os seguintes fragmentos são quase equivalentes:

1. Semáforo

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

Exceto que, no caso nº 2, a trava não pode ser reutilizada e, mais importante, você precisa saber com antecedência quantos threads serão criados (ou espere até que todos sejam iniciados antes de criar a trava).

Então, em que situação a trava pode ser preferível?

Respostas:


109

A trava CountDown é freqüentemente usada para exatamente o oposto do seu exemplo. Geralmente, você teria muitos threads bloqueando em "await ()" que iniciariam todos simultaneamente quando a contagem regressiva chegasse a 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!

Você também pode usar isso como uma "barreira" no estilo MPI que faz com que todos os threads aguardem que outros threads alcancem um determinado ponto antes de prosseguir.

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

Dito isso, a trava CountDown pode ser usada com segurança da maneira que você mostrou em seu exemplo.


1
Obrigado. Portanto, meus dois exemplos não seriam equivalentes se vários threads pudessem esperar na trava ... a menos que sem.acquire (num_threads); é seguido por sem.release (num_threads) ;? Acho que isso os tornaria equivalentes novamente.
finnw

Em certo sentido, sim, contanto que cada thread seja chamada de aquisição seguida de liberação. A rigor, não. Com uma trava, todos os threads são elegíveis para iniciar simultaneamente. Com o semáforo, eles se tornam elegíveis um após o outro (o que pode resultar em agendamento de thread diferente).
James Schek,

A documentação Java parece implicar que um CountdownLatch se encaixa bem com seu exemplo: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . Especificamente, "Um CountDownLatch inicializado em N pode ser usado para fazer um thread esperar até que N threads tenham concluído alguma ação ou alguma ação tenha sido concluída N vezes."
Chris Morris

Você está certo. Atualizarei minha resposta um pouco para refletir que este é o uso mais comum de CountDownLatch que eu vi em comparação com o uso pretendido.
James Schek de

11
Isso responde à pergunta: Qual é o uso mais frequente do CountDownLatch? Ele não responde à pergunta original sobre as vantagens / diferenças de usar um CountDownLatch em vez de um Semaphore.
Marco Lackovic

67

CountDownLatch é usado para iniciar uma série de threads e, em seguida, esperar até que todas sejam concluídas (ou até que chamem countDown()um determinado número de vezes.

O semáforo é usado para controlar o número de threads simultâneos que estão usando um recurso. Esse recurso pode ser algo como um arquivo ou pode ser a cpu, limitando o número de threads em execução. A contagem em um semáforo pode aumentar e diminuir conforme diferentes threads chamam acquire()e release().

Em seu exemplo, você está essencialmente usando o Semaphore como uma espécie de Count UP Latch. Visto que sua intenção é aguardar o término de todos os fios, usar o CountdownLatchtorna sua intenção mais clara.


22

Pequeno resumo:

  1. Semaphore e CountDownLatch têm uma finalidade diferente.

  2. Use o Semaphore para controlar o acesso do thread ao recurso.

  3. Use CountDownLatch para aguardar a conclusão de todos os threads

Definição de semáforo de javadocs:

Um Semaphore mantém um conjunto de licenças. Cada aquisição () bloqueia se necessário até que uma licença esteja disponível e, em seguida, a aceita. Cada release () adiciona uma licença, potencialmente liberando um adquirente de bloqueio.

No entanto, nenhum objeto de licença real é usado; o Semaphore apenas mantém uma contagem do número disponível e age de acordo.

Como funciona ?

Os semáforos são usados ​​para controlar o número de threads simultâneos que estão usando um recurso. Esse recurso pode ser algo como dados compartilhados, ou um bloco de código ( seção crítica ) ou qualquer arquivo.

A contagem em um semáforo pode aumentar e diminuir conforme diferentes threads chamam acquire() e release(). Mas, em qualquer ponto do tempo, você não pode ter mais número de threads maior do que a contagem do Semaphore.

Casos de uso de semáforo:

  1. Limitar o acesso simultâneo ao disco (isso pode prejudicar o desempenho devido a buscas de disco concorrentes)
  2. Limitação de criação de linha
  3. Pooling / limitação de conexão JDBC
  4. Limitação de conexão de rede
  5. Controle de fluxo de CPU ou tarefas intensivas de memória

Dê uma olhada neste artigo para usos de semáforo.

Definição CountDownLatch de javadocs:

Um auxílio de sincronização que permite que um ou mais encadeamentos aguardem até que um conjunto de operações sendo realizadas em outros encadeamentos seja concluído.

Como funciona?

CountDownLatch funciona tendo um contador inicializado com número de threads, que é diminuído cada vez que uma thread completa sua execução. Quando a contagem chega a zero, significa que todos os encadeamentos concluíram sua execução e o encadeamento em espera retoma a execução.

Casos de uso CountDownLatch:

  1. Atingindo o paralelismo máximo: às vezes, queremos iniciar vários threads ao mesmo tempo para atingir o paralelismo máximo
  2. Aguarde N threads para concluir antes de iniciar a execução
  3. Detecção de deadlock.

Dê uma olhada neste artigo para entender os conceitos de CountDownLatch claramente.

Dê uma olhada em Fork Join Pool neste artigo também. Ele tem algumas semelhanças com CountDownLatch .


7

Digamos que você entrou na loja de golfe profissional, esperando encontrar um quarteto,

Quando você fica na fila para obter um tee time de um dos atendentes da loja de artigos esportivos, basicamente você liga proshopVendorSemaphore.acquire(), assim que consegue um tee time, você proshopVendorSemaphore.release()liga. Nota: qualquer um dos atendentes gratuitos pode atender você, ou seja, um recurso compartilhado.

Agora você anda até o starter, ele inicia um CountDownLatch(4)e chama await()para esperar pelos outros, de sua parte você chamou check-in ie CountDownLatch. countDown()e o mesmo acontece com o resto do quarteto. Quando todos chegam, o iniciador dá prosseguimento ( await()devolução da chamada)

Agora, depois de nove buracos, quando cada um de vocês faz uma pausa, hipoteticamente vamos envolver o iniciador novamente, ele usa um 'novo' CountDownLatch(4)para dar a tacada inicial no Buraco 10, a mesma espera / sincronização do Buraco 1.

No entanto, se o starter usou um CyclicBarrierpara começar, ele poderia ter reiniciado a mesma instância no Buraco 10 em vez de uma segunda trava, que usa e lança.


1
Não tenho certeza se entendi sua resposta, mas se você está tentando descrever como o CountdownLatch e o Semaphore funcionam, esse não é o assunto da pergunta.
finnw 01 de

10
Infelizmente, não sei nada sobre golfe.
portador do anel

mas as coisas iniciais também podem ser feitas com .acquire (jogadores) e aumentando a contagem liberada com o lançamento. o countdownlatch parece ter menos funcionalidade e nenhuma capacidade de reutilização.
Lassi Kinnunen

1

Olhando para a fonte disponível gratuitamente, não há mágica na implementação das duas classes, portanto, seu desempenho deve ser o mesmo. Escolha aquele que torna sua intenção mais óbvia.


0

CountdownLatchfaz com que os threads esperem no await()método, até que a contagem chegue a zero. Então, talvez você queira que todos os seus threads esperem até 3 invocações de algo, então todos os threads podem ir. A Latchgeralmente não pode ser redefinida.

Um Semaphorepermite que os threads recuperem permissões, o que impede que muitos threads sejam executados de uma vez, bloqueando se não puder obter a (s) licença (s) necessária (s) para prosseguir. As permissões podem ser devolvidas a um Semaphorepermitindo que os outros threads em espera continuem.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.