multithreading - pthread_mutex_lock - ¿Cuándo se necesita una variable de condición, no es suficiente?
mutex vs semaphore (6)
Aunque puede usarlos de la manera que describe, los mutex no se diseñaron para usarse como un mecanismo de notificación / sincronización. Están destinados a proporcionar acceso mutuamente exclusivo a un recurso compartido. Usar mutexes para señalar una condición es incómodo y supongo que se vería algo así (donde Thread1 es señalado por Thread2):
Thread1:
while(1) {
lock(mutex); // Blocks waiting for notification from Thread2
... // do work after notification is received
unlock(mutex); // Tells Thread2 we are done
}
Thread2:
while(1) {
... // do the work that precedes notification
unlock(mutex); // unblocks Thread1
lock(mutex); // lock the mutex so Thread1 will block again
}
Hay varios problemas con esto:
- Thread2 no puede continuar "haciendo el trabajo que precede a la notificación" hasta que Thread1 haya terminado con "trabajo después de la notificación". Con este diseño, Thread2 ni siquiera es necesario, es decir, ¿por qué no mover "trabajo que precede" y "trabajar después de la notificación" al mismo hilo, ya que solo uno puede ejecutarse en un momento determinado?
- Si Thread2 no puede adelantarse a Thread1, Thread1 volverá a bloquear el mutex cuando repita el ciclo while (1) y Thread1 realizará el "trabajo después de la notificación" aunque no haya notificación. Esto significa que debe de alguna manera garantizar que Thread2 bloqueará el mutex antes de que lo haga Thread1. ¿Cómo haces eso? Tal vez forzar un evento de agenda durmiendo o por algún otro medio específico del sistema operativo, pero incluso esto no se garantiza que funcione en función del tiempo, su sistema operativo y el algoritmo de programación.
Estos dos problemas no son menores, de hecho, ambos son defectos importantes de diseño y errores latentes. El origen de estos dos problemas es el requisito de que un mutex esté bloqueado y desbloqueado dentro del mismo hilo. Entonces, ¿cómo evitar los problemas anteriores? ¡Usa variables de condición!
Por cierto, si sus necesidades de sincronización son realmente simples, podría usar un semáforo antiguo simple que evite la complejidad adicional de las variables de condición.
Estoy seguro de que mutex no es suficiente, esa es la razón por la cual existe el concepto de las variables de condición; pero me supera y no soy capaz de convencerme a mí mismo con un escenario concreto cuando una variable de condición es esencial.
Las diferencias entre las respuestas condicionales de las variables condicionales , mutexes y bloqueos indican que una variable de condición es una
bloqueo con un mecanismo de "señalización". Se usa cuando los hilos necesitan esperar a que un recurso esté disponible. Un hilo puede "esperar" en un CV y luego el productor de recursos puede "señalar" la variable, en cuyo caso los hilos que esperan el CV serán notificados y pueden continuar la ejecución.
Lo que me confunde es que un hilo también puede esperar en un mutex, y cuando se señala, simplemente significa que la variable ya está disponible, ¿por qué necesitaría una variable de condición?
PD: Además, se requiere un mutex para proteger la variable de condición de todos modos, cuando mi visión es más oblicua para no ver el propósito de la variable de condición.
Creo que es implementación definida.
El mutex es suficiente o no depende de si considera el mutex como un mecanismo para secciones críticas o algo más.
Como se menciona en http://en.cppreference.com/w/cpp/thread/mutex/unlock ,
El mutex debe estar bloqueado por el hilo de ejecución actual, de lo contrario, el comportamiento no está definido.
lo que significa que un hilo solo podría desbloquear un mutex que fue bloqueado / propiedad por sí mismo en C ++.
Pero en otros lenguajes de programación, es posible que pueda compartir un mutex entre procesos.
Por lo tanto, distinguir los dos conceptos puede ser solo consideraciones de rendimiento, una identificación de propiedad compleja o el intercambio entre procesos no son válidos para aplicaciones simples.
Por ejemplo, puede corregir el caso de @ slowjelj con un mutex adicional (podría ser una solución incorrecta):
Thread1:
lock(mutex0);
while(1) {
lock(mutex0); // Blocks waiting for notification from Thread2
... // do work after notification is received
unlock(mutex1); // Tells Thread2 we are done
}
Thread2:
while(1) {
lock(mutex1); // lock the mutex so Thread1 will block again
... // do the work that precedes notification
unlock(mutex0); // unblocks Thread1
}
Pero su programa se quejará de que ha activado una afirmación dejada por el compilador (por ejemplo, "desbloqueo de mutex sin propietario" en Visual Studio 2015).
Estaba pensando en esto también y la información más importante, que me faltaba en todas partes, es que mutex puede poseer (y cambiar) en ese momento solo un hilo. Entonces, si tiene un productor y más consumidores, el productor tendría que esperar mutex para producir. Con cond. variable que puede producir en cualquier momento.
La var condicional y el par mutex pueden reemplazarse por un semáforo binario y un par mutex. La secuencia de operaciones de un hilo de consumidor al usar el var varxto condicional es:
Bloquear el mutex
Espere en la var condicional
Proceso
Desbloquee el mutex
La secuencia de operaciones del hilo productor es
Bloquear el mutex
Señale la var condicional
Desbloquee el mutex
La secuencia de hilo de consumidor correspondiente cuando se usa el par de mute de sema + es
Espera en el sema binario
Bloquear el mutex
Verificar la condición esperada
Si la condición es verdadera, procesa.
Desbloquee el mutex
Si la verificación de condición en el paso 3 era falsa, regrese al paso 1.
La secuencia para el hilo productor es:
Bloquear el mutex
Publica el sema binario
Desbloquee el mutex
Como puede ver, el procesamiento incondicional en el paso 3 cuando se usa la var condicional se reemplaza por el procesamiento condicional en el paso 3 y el paso 4 cuando se usa el sema binario.
La razón es que cuando se usa sema + mutex, en una condición de carrera, otro hilo de consumidor puede colarse entre los pasos 1 y 2 y procesar / anular la condición. Esto no sucederá cuando se usa var condicional. Cuando se usa la var condicional, la condición se garantiza como verdadera después del paso 2.
El semáforo binario se puede reemplazar con el semáforo de conteo regular. Esto puede dar como resultado el paso 6 al paso 1 repetir algunas veces más.
Mutex es para acceso exclusivo de recurso compartido, mientras que la variable condicional es para esperar que una condición sea verdadera. La gente puede pensar que puede implementar variables condicionales sin el soporte de kernel. Una solución común que uno podría encontrar es la siguiente: "flag + mutex":
lock(mutex)
while (!flag) {
sleep(100);
}
unlock(mutex)
do_something_on_flag_set();
pero nunca funcionará, porque nunca liberas el mutex durante la espera, nadie más puede configurar la bandera de una manera segura para la ejecución de subprocesos. Esta es la razón por la cual necesitamos una variable condicional, cuando esperas una variable de condición, la secuencia de muteos asociada no se retiene por tu hilo hasta que se señalice.
Necesita variables de condición, para usar con un mutex (cada cond.var. Pertenece a un mutex) para señalar los estados cambiantes (condiciones) de un hilo a otro. La idea es que un hilo puede esperar hasta que alguna condición se haga realidad. Dichas condiciones son específicas del programa (es decir, "la cola está vacía", "la matriz es grande", "algunos recursos están casi agotados", "algunos pasos de computación han terminado", etc.). Un mutex puede tener varias variables de condición relacionadas. Y necesita variables de condición porque tales condiciones no siempre se pueden expresar tan simplemente como "un mutex está bloqueado" (por lo que necesita transmitir los cambios en las condiciones a otros hilos).
Lea algunos buenos tutoriales de posix thread, por ejemplo, este tutorial o that o that . Mejor aún, lea un buen libro de subprocesos. Ver esta pregunta
Lea también Programación avanzada de Unix y Programación avanzada de Linux
PS Paralelismo e hilos son conceptos difíciles de comprender. Tómese el tiempo para leer, experimentar y leer nuevamente.