CountDownLatch vs.Semaphore

92

¿Hay alguna ventaja de usar

java.util.concurrent.CountdownLatch

en vez de

java.util.concurrent.Semaphore ?

Por lo que puedo decir, los siguientes fragmentos son casi 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();

Excepto que en el caso n. ° 2, el pestillo no se puede reutilizar y, lo que es más importante, debe saber de antemano cuántos subprocesos se crearán (o esperar hasta que se inicien antes de crear el pestillo).

Entonces, ¿en qué situación sería preferible el pestillo?

finnw
fuente

Respuestas:

109

El pestillo CountDown se usa con frecuencia para exactamente lo contrario de su ejemplo. Generalmente, tendría muchos hilos bloqueados en "await ()" que comenzarían todos simultáneamente cuando el countown llegara a cero.

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!

También puede utilizar esto como una "barrera" de estilo MPI que hace que todos los subprocesos esperen a que otros subprocesos se pongan al día hasta cierto punto antes de continuar.

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

Dicho todo esto, el pestillo CountDown se puede utilizar de forma segura de la manera que ha mostrado en su ejemplo.

James Schek
fuente
1
Gracias. Entonces, mis dos ejemplos no serían equivalentes si varios subprocesos pudieran esperar en el pestillo ... a menos que sem.acquire (num_threads); va seguido de sem.release (num_threads) ;? Creo que eso los volvería equivalentes.
finnw
En cierto sentido, sí, siempre y cuando cada hilo llamado adquirir seguido de liberación. Estrictamente hablando, no. Con un pestillo, todos los subprocesos pueden iniciarse simultáneamente. Con el semáforo, se vuelven elegibles uno tras otro (lo que podría resultar en una programación de subprocesos diferente).
James Schek
La documentación de Java parece implicar que este CountdownLatch encaja bien con su ejemplo: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . Específicamente, "Un CountDownLatch inicializado en N puede usarse para hacer que un subproceso espere hasta que N subprocesos hayan completado alguna acción, o alguna acción se haya completado N veces".
Chris Morris
Tienes razón. Actualizaré mi respuesta un poco para reflejar que estos son los usos más comunes de CountDownLatch que he visto frente a que es el uso previsto.
James Schek
11
Esto responde a la pregunta ¿Cuál es el uso más frecuente de CountDownLatch? No responde a la pregunta original sobre las ventajas / diferencias de usar un CountDownLatch sobre un Semaphore.
Marco Lackovic
67

CountDownLatch se usa para iniciar una serie de subprocesos y luego esperar hasta que todos estén completos (o hasta que llamen countDown()un número determinado de veces.

El semáforo se utiliza para controlar el número de subprocesos simultáneos que utilizan un recurso. Ese recurso puede ser algo así como un archivo, o podría ser la CPU al limitar la cantidad de subprocesos que se ejecutan. El recuento de un semáforo puede subir y bajar a medida que diferentes hilos llaman acquire()y release().

En su ejemplo, esencialmente está usando Semaphore como una especie de Count UP Latch. Dado que su intención es esperar a que terminen todos los hilos, usar el CountdownLatchhace que su intención sea más clara.

mtruesdell
fuente
22

Breve resumen:

  1. Semaphore y CountDownLatch tienen un propósito diferente.

  2. Utilice Semaphore para controlar el acceso del hilo al recurso.

  3. Utilice CountDownLatch para esperar la finalización de todos los hilos

Definición de semáforo de javadocs:

Un semáforo mantiene un conjunto de permisos. Cada uno adquiere () bloquea si es necesario hasta que haya un permiso disponible y luego lo toma. Cada versión () agrega un permiso, lo que potencialmente libera a un adquirente bloqueante.

Sin embargo, no se utilizan objetos de permiso reales; el semáforo solo lleva un recuento del número disponible y actúa en consecuencia.

Como funciona ?

Los semáforos se utilizan para controlar la cantidad de subprocesos simultáneos que están usando un recurso. Ese recurso puede ser algo así como un dato compartido, un bloque de código ( sección crítica ) o cualquier archivo.

El recuento de un semáforo puede subir y bajar a medida que diferentes hilos llaman acquire() y release(). Pero en cualquier momento, no puede tener más subprocesos que el recuento de semáforos.

Casos de uso de semáforos:

  1. Limitar el acceso simultáneo al disco (esto puede matar el rendimiento debido a búsquedas de disco en competencia)
  2. Limitación de la creación de hilos
  3. Agrupación / limitación de conexiones JDBC
  4. Limitación de la conexión de red
  5. Limitación de tareas intensivas en memoria o CPU

Eche un vistazo a este artículo para conocer los usos de los semáforos.

CountDownLatch definición de javadocs:

Una ayuda de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones que se están realizando en otros subprocesos.

¿Como funciona?

CountDownLatch funciona al tener un contador inicializado con el número de subprocesos, que se reduce cada vez que un subproceso completa su ejecución. Cuando el recuento llega a cero, significa que todos los subprocesos han completado su ejecución y el subproceso que espera en el pestillo reanuda la ejecución.

Casos de uso de CountDownLatch:

  1. Lograr el máximo paralelismo: a veces queremos iniciar una serie de subprocesos al mismo tiempo para lograr el máximo paralelismo
  2. Espere N subprocesos para completar antes de iniciar la ejecución
  3. Detección de interbloqueo.

Eche un vistazo a este artículo para comprender claramente los conceptos de CountDownLatch.

Eche un vistazo a Fork Join Pool en este artículo también. Tiene algunas similitudes con CountDownLatch .

Ravindra babu
fuente
7

Digamos que entró en la tienda de golf con la esperanza de encontrar un cuarteto,

Cuando hace fila para obtener un tee time de uno de los asistentes de la tienda profesional, básicamente llama proshopVendorSemaphore.acquire(), una vez que obtiene un tee time, llama. proshopVendorSemaphore.release()Nota: cualquiera de los asistentes gratuitos puede atenderlo, es decir, recurso compartido.

Ahora camina hacia el arranque, comienza un CountDownLatch(4) y llama await()para esperar a los demás, por tu parte, llamaste registrado, es decir CountDownLatch. countDown()y también el resto del cuarteto. Cuando llegan todos, el motor de arranque da marcha ( await()devuelve la llamada)

Ahora, después de nueve hoyos cuando cada uno de ustedes se toma un descanso, hipotéticamente involucremos al abridor nuevamente, él usa un 'nuevo' CountDownLatch(4)para comenzar el hoyo 10, la misma espera / sincronización que el hoyo 1.

Sin embargo, si el abridor usó un CyclicBarrierpara empezar, podría haber reiniciado la misma instancia en el hoyo 10 en lugar de un segundo pestillo, que usa & throw.

Raj Srinivas
fuente
1
No estoy seguro de entender su respuesta, pero si está intentando describir cómo funcionan CountdownLatch y Semaphore, ese no es el tema de la pregunta.
finnw
10
Lamentablemente no sé nada de golf.
portador del anillo
pero las cosas iniciales también se podrían hacer con .acquire (jugadores) y aumentando el recuento de reproducciones con el lanzamiento. el cierre de cuenta atrás parece tener menos funcionalidad y no se puede volver a utilizar.
Lassi Kinnunen
1

En cuanto a la fuente disponible gratuitamente, no hay magia en la implementación de las dos clases, por lo que su rendimiento debería ser muy similar. Elija el que haga más obvia su intención.

Tom Hawtin - tackline
fuente
0

CountdownLatchhace que los subprocesos esperen en el await()método, hasta que el recuento llegue a cero. Entonces, tal vez quieras que todos tus hilos esperen hasta 3 invocaciones de algo, entonces todos los hilos pueden irse. Un Latchgeneral no se puede restablecer.

A Semaphorepermite que los subprocesos recuperen permisos, lo que evita que se ejecuten demasiados subprocesos a la vez, bloqueándose si no puede obtener los permisos que requiere para continuar. Los permisos se pueden devolver para Semaphorepermitir que los otros hilos en espera continúen.

Spencer Kormos
fuente