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?

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.


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.


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 .


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.


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.


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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.