java - CountDownLatch vs. Semaphore
multithreading concurrency (6)
¿Hay alguna ventaja de usar
java.util.concurrent.CountdownLatch
en lugar 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, no se puede reutilizar el pestillo y, lo que es más importante, debe saber de antemano cuántos subprocesos se crearán (o esperar hasta que se hayan iniciado antes de crear el pestillo).
Entonces, ¿en qué situación podría ser preferible el cierre?
CountDown latch se utiliza con frecuencia para el opuesto exacto de su ejemplo. Generalmente, tendrías muchos hilos bloqueando en "await ()" que todos comenzarían 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 podría usar esto como una "barrera" estilo MPI que hace que todos los hilos esperen a que otros hilos alcancen 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 usar de manera segura de la manera que se ha mostrado en su ejemplo.
CountdownLatch hace que los hilos esperen en el método await (), hasta el momento en que el conteo haya llegado a cero. Así que tal vez quieras que todos tus hilos esperen hasta 3 invocaciones de algo, entonces todos los hilos pueden irse. Un pestillo en general no se puede restablecer.
Un semáforo permite que los hilos recuperen permisos, lo que evita que se ejecuten demasiados hilos a la vez, bloqueando si no puede obtener los permisos que requiere para proceder. Los permisos pueden devolverse a un semáforo permitiendo que los otros hilos de espera continúen.
Digamos que entraste a la tienda profesional de golf, esperando encontrar un cuarteto,
Cuando hace una fila para obtener una hora de salida de uno de los asistentes de la tienda profesional, básicamente llamó a proshopVendorSemaphore.acquire()
, una vez que obtiene una hora de salida, llamó a proshopVendorSemaphore.release()
.Nota: cualquiera de los asistentes gratuitos puede servicio, es decir, recurso compartido.
Ahora camina hasta el principio, inicia un CountDownLatch(4)
y llama a await()
para esperar a los demás, por su parte usted llamó check-in, es decir CountDownLatch
. countDown()
y también el resto del cuarteto. Cuando todos llegan, el arrancador da marcha adelante (llamadas a la await()
devuelve)
Ahora, después de nueve hoyos en los que cada uno de ustedes toma un descanso e hipotéticamente involucra el arranque nuevamente, usa un ''nuevo'' CountDownLatch(4)
para jugar en el Hoyo 10, la misma espera / sincronización que en el Hoyo 1.
Sin embargo, si el iniciador usó un CyclicBarrier
para empezar, podría haber reiniciado la misma instancia en el Hoyo 10 en lugar de un segundo enganche, que usa & throw.
Si observamos la fuente disponible de manera gratuita, no hay magia en la implementación de las dos clases, por lo que su rendimiento debería ser muy similar. Elija el que hace que su intención sea más obvia.
java.util.concurrent.CountdownLatch se utiliza para iniciar una serie de subprocesos y luego esperar hasta que todos estén completos (o hasta que invoquen countDown()
un número determinado de veces.
Semaphore se usa para controlar la cantidad de subprocesos concurrentes que usan un recurso. Ese recurso puede ser algo así como un archivo, o podría ser la CPU al limitar el número de subprocesos que se ejecutan. El recuento en un semáforo puede subir y bajar a medida que diferentes hilos llaman a acquire()
y release()
.
En su ejemplo, básicamente está utilizando Semaphore como un tipo de Cierre de cuenta hacia arriba . Dado que tu intención es esperar a que todos los hilos terminen, usar CountdownLatch
aclara tu intención.
Breve resumen:
java.util.concurrent.Semaphore y java.util.concurrent.CountdownLatch un propósito diferente.
Use Semáforo para controlar el acceso de subprocesos al recurso.
Use CountDownLatch para esperar la finalización de todos los hilos
Definición del semáforo de javadocs:
Un semáforo mantiene un conjunto de permisos. Cada adquisición () bloquea si es necesario hasta que un permiso esté disponible, y luego lo toma. Cada versión () agrega un permiso, liberando potencialmente un adquirente de bloqueo.
Sin embargo, no se usan objetos de permiso reales; el semáforo solo cuenta el número disponible y actúa en consecuencia.
Como funciona ?
Los semáforos se utilizan para controlar el número de subprocesos simultáneos que utilizan un recurso. Ese recurso puede ser algo así como un dato compartido, o un bloque de código ( sección crítica ) o cualquier archivo.
El recuento en un semáforo puede subir y bajar a medida que diferentes hilos llaman a acquire
() y release
(). Pero en cualquier punto del tiempo, no puede tener más hilos mayores que el contaje del semáforo.
Casos de uso de semáforo:
- Limitar el acceso concurrente al disco (esto puede matar el rendimiento debido a las búsquedas de disco competidoras)
- Thread creation limitting
- Agrupación / limitación de conexiones JDBC
- Regulación de la conexión de red
- Estrangulamiento de CPU o tareas intensivas de memoria
Eche un vistazo a este article para los usos del semáforo.
Definición de CountDownLatch desde javadocs:
Un asistente de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones en otros subprocesos.
¿Como funciona?
CountDownLatch funciona al tener un contador inicializado con el número de subprocesos, que se decrementa cada vez que un subproceso completa su ejecución. Cuando el conteo llega a cero, significa que todos los hilos han completado su ejecución, y el hilo en espera en el cierre reanuda la ejecución.
Casos de uso de CountDownLatch:
- Alcanzar el Paralelismo Máximo: A veces queremos comenzar una cantidad de hilos al mismo tiempo para lograr el máximo paralelismo
- Espere N hilos para completar antes de iniciar la ejecución
- Detección de punto muerto
Eche un vistazo a este article para entender los conceptos de CountDownLatch claramente.
Eche un vistazo a Fork Join Pool en este article también. Tiene algunas similitudes con CountDownLatch .