Respuestas:
Los candados se utilizan para la exclusión mutua. Cuando desee asegurarse de que un fragmento de código sea atómico, coloque un candado alrededor. Teóricamente, podría usar un semáforo binario para hacer esto, pero ese es un caso especial.
Los semáforos y las variables de condición se basan en la exclusión mutua que proporcionan los bloqueos y se utilizan para proporcionar acceso sincronizado a los recursos compartidos. Se pueden utilizar para fines similares.
Una variable de condición se usa generalmente para evitar la espera ocupada (bucle repetidamente mientras se verifica una condición) mientras se espera que un recurso esté disponible. Por ejemplo, si tiene un hilo (o varios hilos) que no pueden continuar hasta que una cola esté vacía, el enfoque de espera ocupada sería simplemente hacer algo como:
//pseudocode
while(!queue.empty())
{
sleep(1);
}
El problema con esto es que está perdiendo el tiempo del procesador al hacer que este hilo verifique repetidamente la condición. ¿Por qué no, en cambio, tener una variable de sincronización que se pueda señalar para indicarle al hilo que el recurso está disponible?
//pseudocode
syncVar.lock.acquire();
while(!queue.empty())
{
syncVar.wait();
}
//do stuff with queue
syncVar.lock.release();
Presumiblemente, tendrá un hilo en otro lugar que está sacando cosas de la cola. Cuando la cola está vacía, puede llamar syncVar.signal()
para despertar un hilo aleatorio que está dormido syncVar.wait()
(o generalmente también hay un método signalAll()
o broadcast()
para despertar todos los hilos que están esperando).
Generalmente uso variables de sincronización como esta cuando tengo uno o más subprocesos esperando en una sola condición en particular (por ejemplo, que la cola esté vacía).
Los semáforos se pueden usar de manera similar, pero creo que se usan mejor cuando tienes un recurso compartido que puede estar disponible y no disponible en función de un número entero de cosas disponibles. Los semáforos son buenos para situaciones de productor / consumidor donde los productores están asignando recursos y los consumidores los consumen.
Piense si tuviera una máquina expendedora de refrescos. Solo hay una máquina de refrescos y es un recurso compartido. Tiene un hilo que es un proveedor (productor) que es responsable de mantener la máquina almacenada y N hilos que son compradores (consumidores) que quieren sacar refrescos de la máquina. La cantidad de refrescos en la máquina es el valor entero que impulsará nuestro semáforo.
Cada hilo de comprador (consumidor) que llega a la máquina de refrescos llama al down()
método del semáforo para tomar un refresco. Esto tomará un refresco de la máquina y reducirá el recuento de refrescos disponibles en 1. Si hay refrescos disponibles, el código seguirá pasando la down()
declaración sin ningún problema. Si no hay refrescos disponibles, el hilo dormirá aquí esperando que se le notifique cuando los refrescos estén disponibles nuevamente (cuando haya más refrescos en la máquina).
El hilo del proveedor (productor) estaría esencialmente esperando que la máquina de refrescos esté vacía. El vendedor recibe una notificación cuando se saca el último refresco de la máquina (y uno o más consumidores están esperando potencialmente a sacar los refrescos). El proveedor reabastecería la máquina de refrescos con el up()
método del semáforo , el número disponible de refrescos se incrementaría cada vez y, por lo tanto, se notificaría a los subprocesos de consumidores en espera de que hay más refrescos disponibles.
Los métodos wait()
y signal()
de una variable de sincronización tienden a estar ocultos dentro de las operaciones down()
y up()
del semáforo.
Ciertamente, existe una superposición entre las dos opciones. Hay muchos escenarios en los que un semáforo o una variable de condición (o un conjunto de variables de condición) podrían servir para sus propósitos. Tanto los semáforos como las variables de condición están asociados con un objeto de bloqueo que utilizan para mantener la exclusión mutua, pero luego brindan una funcionalidad adicional además del bloqueo para sincronizar la ejecución del hilo. Depende principalmente de usted descubrir cuál tiene más sentido para su situación.
Esa no es necesariamente la descripción más técnica, pero así es como tiene sentido en mi cabeza.
Revelemos qué hay debajo del capó.
La variable condicional es esencialmente una cola de espera , que admite operaciones de espera de bloqueo y activación, es decir, puede poner un hilo en la cola de espera y establecer su estado en BLOQUEO, y sacar un hilo de él y establecer su estado en LISTO.
Tenga en cuenta que para usar una variable condicional, se necesitan otros dos elementos:
El protocolo entonces se convierte en,
El semáforo es esencialmente un contador + un mutex + una cola de espera. Y se puede utilizar tal cual sin dependencias externas. Puede usarlo como mutex o como variable condicional.
Por lo tanto, el semáforo puede tratarse como una estructura más sofisticada que la variable condicional, mientras que la última es más ligera y flexible.
Los semáforos se pueden utilizar para implementar el acceso exclusivo a las variables, sin embargo, están destinados a ser utilizados para la sincronización. Los muttex, por otro lado, tienen una semántica que está estrictamente relacionada con la exclusión mutua: solo el proceso que bloqueó el recurso puede desbloquearlo.
Desafortunadamente, no puede implementar la sincronización con mutex, por eso tenemos variables de condición. También observe que con las variables de condición puede desbloquear todos los hilos en espera en el mismo instante usando el desbloqueo de transmisión. Esto no se puede hacer con semáforos.
las variables de semáforo y condición son muy similares y se utilizan principalmente para los mismos propósitos. Sin embargo, existen pequeñas diferencias que podrían hacer que uno sea preferible. Por ejemplo, para implementar la sincronización de barrera no sería posible usar un semáforo, pero una variable de condición es ideal.
La sincronización de barrera es cuando desea que todos sus hilos esperen hasta que todos hayan llegado a una determinada parte de la función del hilo. esto se puede implementar al tener una variable estática que es inicialmente el valor del total de subprocesos decrecido por cada subproceso cuando alcanza esa barrera. esto significaría que queremos que cada hilo duerma hasta que llegue el último. ¡Un semáforo haría exactamente lo contrario! con un semáforo, cada subproceso seguiría ejecutándose y el último subproceso (que establecerá el valor del semáforo en 0) se suspenderá.
por otro lado, una variable de condición es ideal. cuando cada hilo llega a la barrera comprobamos si nuestro contador estático es cero. si no, configuramos el hilo para dormir con la función de espera de la variable de condición. cuando el último hilo llega a la barrera, el valor del contador se reducirá a cero y este último hilo llamará a la función de señal de variable de condición que despertará a todos los demás hilos.
Archivo variables de condición bajo sincronización del monitor. En general, he visto semáforos y monitores como dos estilos de sincronización diferentes. Existen diferencias entre los dos en términos de cuántos datos de estado se mantienen inherentemente y cómo desea modelar el código, pero en realidad no hay ningún problema que pueda ser resuelto por uno pero no por el otro.
Tiendo a codificar hacia la forma del monitor; en la mayoría de los lenguajes en los que trabajo, eso se reduce a mutex, variables de condición y algunas variables de estado de respaldo. Pero los semáforos también harían el trabajo.
El mutex
y conditional variables
son heredados de semaphore
.
mutex
, el semaphore
usa dos estados: 0, 1condition variables
el semaphore
contador de usos.Son como azúcar sintáctico