Es solo la forma en que las variables de condición se implementan (o fueron originalmente).
El mutex se utiliza para proteger la variable de condición en sí . Por eso es necesario que lo bloquees antes de esperar.
La espera "atómicamente" desbloqueará el mutex, permitiendo que otros accedan a la variable de condición (para señalización). Luego, cuando se señala o se transmite la variable de condición, se despertará uno o más subprocesos en la lista de espera y el mutex se bloqueará mágicamente nuevamente para ese subproceso.
Por lo general, ve la siguiente operación con variables de condición, que ilustra cómo funcionan. El siguiente ejemplo es un subproceso de trabajo al que se le da trabajo mediante una señal a una variable de condición.
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
do the work.
unlock mutex.
clean up.
exit thread.
El trabajo se realiza dentro de este ciclo siempre que haya algo disponible cuando regrese la espera. Cuando el hilo se ha marcado para dejar de funcionar (normalmente otro hilo configura la condición de salida y luego patea la variable de condición para activar este hilo), el bucle saldrá, el mutex se desbloqueará y este hilo saldrá.
El código anterior es un modelo de consumidor único ya que el mutex permanece bloqueado mientras se realiza el trabajo. Para una variación de múltiples consumidores, puede usar, como ejemplo :
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
copy work to thread local storage.
unlock mutex.
do the work.
lock mutex.
unlock mutex.
clean up.
exit thread.
lo que permite que otros consumidores reciban trabajo mientras este está trabajando.
La variable de condición lo libera de la carga de sondear alguna condición, permitiendo que otro hilo le notifique cuando algo debe suceder. Otro hilo puede decir que ese hilo que funciona está disponible de la siguiente manera:
lock mutex.
flag work as available.
signal condition variable.
unlock mutex.
La gran mayoría de lo que a menudo se llama erróneamente despertares espurios generalmente se debe a que se han señalado múltiples hilos dentro de su pthread_cond_wait
llamada (transmisión), uno volvería con el mutex, haría el trabajo y luego volvería a esperar.
Entonces el segundo hilo señalado podría salir cuando no había trabajo que hacer. Por lo tanto, tenía que tener una variable adicional que indicara que el trabajo debería hacerse (esto estaba inherentemente protegido por mutex con el par condvar / mutex aquí; sin embargo, se necesitaban otros hilos para bloquear el mutex antes de cambiarlo).
Que era técnicamente imposible que un hilo para volver a una condición de espera sin ser expulsado por otro proceso (esto es un verdadero despertar espuria), pero, en todos mis muchos años trabajando en pthreads, tanto en el desarrollo / servicio del código y como usuario De ellos, nunca recibí uno de estos. Tal vez eso fue solo porque HP tuvo una implementación decente :-)
En cualquier caso, el mismo código que manejó el caso erróneo también manejó despertadores falsos genuinos, ya que el indicador de trabajo disponible no se establecería para esos.
do something
dentro delwhile
bucle?Una variable de condición es bastante limitada si solo puede señalar una condición, por lo general, necesita manejar algunos datos relacionados con la condición que se señaló. La señalización / activación debe hacerse atómicamente para lograr eso sin introducir condiciones de carrera, o ser demasiado complejo
pthreads también puede proporcionarle, por razones bastante técnicas, una activación espuria . Eso significa que debe verificar un predicado, de modo que pueda estar seguro de que la condición realmente se señaló, y distinguirlo de una vigilia espuria. La verificación de dicha condición en lo que respecta a esperar debe ser protegida, por lo que una variable de condición necesita una forma de esperar / despertar atómicamente mientras se bloquea / desbloquea un mutex que protege esa condición.
Considere un ejemplo simple donde se le notifica que se producen algunos datos. Tal vez otro hilo creó algunos datos que desea y estableció un puntero a esos datos.
Imagine un hilo productor que da algunos datos a otro hilo consumidor a través de un puntero 'some_data'.
naturalmente, obtendrías muchas condiciones de carrera, ¿qué pasaría si el otro hilo lo hiciera
some_data = new_data
justo después de que te despertaran, pero antes de que lo hicieras?data = some_data
Realmente tampoco puedes crear tu propio mutex para proteger este caso .eg
No funcionará, todavía hay una posibilidad de una condición de carrera entre despertarse y agarrar el mutex. Colocar el mutex antes de pthread_cond_wait no lo ayuda, ya que ahora mantendrá el mutex mientras espera, es decir, el productor nunca podrá tomar el mutex. (tenga en cuenta que, en este caso, podría crear una segunda variable de condición para indicarle al productor que ha terminado
some_data
, aunque esto se volverá complejo, especialmente si desea muchos productores / consumidores).Por lo tanto, necesita una forma de liberar / agarrar atómicamente el mutex cuando espera / se despierta de la condición. Eso es lo que hace las variables de condición pthread, y esto es lo que haría:
(el productor naturalmente necesitaría tomar las mismas precauciones, siempre protegiendo 'some_data' con el mismo mutex, y asegurándose de que no sobrescriba some_data si some_data está actualmente! = NULL)
fuente
while (some_data != NULL)
ser un ciclo do-while para que espere la variable de condición al menos una vez?while(some_data != NULL)
serwhile(some_data == NULL)
?Las variables de condición POSIX no tienen estado. Por lo tanto, es su responsabilidad mantener el estado. Dado que tanto los subprocesos que esperan como los subprocesos que le dicen a otros subprocesos que dejen de esperar, deben estar protegidos por un mutex. Si cree que puede usar variables de condición sin un mutex, entonces no ha comprendido que las variables de condición no tienen estado.
Las variables de condición se construyen alrededor de una condición. Los subprocesos que esperan en una variable de condición están esperando alguna condición. Los hilos que indican que las variables de condición cambian esa condición. Por ejemplo, un hilo podría estar esperando que lleguen algunos datos. Algún otro hilo podría notar que los datos han llegado. "La información ha llegado" es la condición.
Aquí está el uso clásico de una variable de condición, simplificado:
Vea cómo el hilo espera trabajo. El trabajo está protegido por un mutex. La espera libera el mutex para que otro hilo pueda darle algo de trabajo a este hilo. Así es como se señalaría:
Tenga en cuenta que necesita el mutex para proteger la cola de trabajo. Tenga en cuenta que la variable de condición en sí no tiene idea de si hay trabajo o no. Es decir, una variable de condición debe estar asociada con una condición, esa condición debe ser mantenida por su código y, dado que se comparte entre subprocesos, debe estar protegida por un mutex.
fuente
No todas las funciones variables de condición requieren un mutex: solo las operaciones de espera. Las operaciones de señal y transmisión no requieren mutex. Una variable de condición tampoco está asociada permanentemente con un mutex específico; el mutex externo no protege la variable de condición. Si una variable de condición tiene un estado interno, como una cola de subprocesos en espera, esto debe estar protegido por un bloqueo interno dentro de la variable de condición.
Las operaciones de espera reúnen una variable de condición y un mutex, porque:
Por esta razón, la operación de espera toma como argumentos tanto el mutex como la condición: para que pueda gestionar la transferencia atómica de un subproceso desde poseer el mutex hasta esperar, para que el subproceso no sea víctima de la condición de carrera de activación perdida .
Se producirá una condición de carrera de activación perdida si un subproceso abandona un mutex y luego espera en un objeto de sincronización sin estado, pero de una manera que no es atómica: existe una ventana de tiempo cuando el subproceso ya no tiene el bloqueo y tiene Aún no ha comenzado a esperar el objeto. Durante esta ventana, puede entrar otro subproceso, hacer que la condición esperada sea verdadera, señalar la sincronización sin estado y luego desaparecer. El objeto sin estado no recuerda que se haya señalado (no tiene estado). Entonces, el subproceso original se duerme en el objeto de sincronización sin estado y no se activa, a pesar de que la condición que necesita ya se ha convertido en realidad: la activación perdida.
Las funciones de espera de condición variable evitan la activación perdida asegurándose de que el hilo de llamada esté registrado para capturar de manera confiable la activación antes de que abandone el mutex. Esto sería imposible si la función de espera de la variable de condición no tomara el mutex como argumento.
fuente
pthread_cond_broadcast
y laspthread_cond_signal
operaciones (de las que trata esta pregunta SO) ni siquiera toman el mutex como argumento; Solo la condición. La especificación POSIX está aquí . El mutex solo se menciona en referencia a lo que sucede en los hilos de espera cuando se despiertan.No encuentro que las otras respuestas sean tan concisas y legibles como esta página . Normalmente el código de espera se ve así:
Hay tres razones para envolverlo
wait()
en un mutex:signal()
anteswait()
y nos perderíamos este despertar.check()
depende de la modificación de otro hilo, por lo que necesita exclusión mutua de todos modos.El tercer punto no siempre es preocupante: el contexto histórico está vinculado desde el artículo a esta conversación .
A menudo se mencionan espurias despertares con respecto a este mecanismo (es decir, el hilo de espera se despierta sin
signal()
ser llamado). Sin embargo, tales eventos son manejados por el buclecheck()
.fuente
Las variables de condición están asociadas con un mutex porque es la única forma en que puede evitar la carrera que está diseñada para evitar.
En este punto, no hay ningún subproceso que indique la variable de condición, por lo que el subproceso 1 esperará para siempre, a pesar de que protectedReadyToRunVariable dice que está listo.
La única forma de evitar esto es que las variables de condición liberen atómicamente el mutex mientras simultáneamente comienzan a esperar en la variable de condición. Es por eso que la función cond_wait requiere un mutex
fuente
Se supone que el mutex está bloqueado cuando llamas
pthread_cond_wait
; cuando lo llamas atómicamente, desbloquea el mutex y luego bloquea la condición. Una vez que se señala la condición, la bloquea atómicamente de nuevo y regresa.Esto permite la implementación de una programación predecible si se desea, ya que el hilo que estaría haciendo la señalización puede esperar hasta que se libere el mutex para hacer su procesamiento y luego señalar la condición.
fuente
Hice un ejercicio en clase si quieres un ejemplo real de variable de condición:
fuente
Parece ser una decisión de diseño específica más que una necesidad conceptual.
Según los documentos de pthreads, la razón por la que el mutex no se separó es porque hay una mejora significativa en el rendimiento al combinarlos y esperan que, debido a las condiciones de carrera comunes, si no usas un mutex, casi siempre se hará de todos modos.
https://linux.die.net/man/3/pthread_cond_wait
fuente
Hay un montón de exégesis sobre eso, pero quiero resumirlo con un ejemplo a continuación.
¿Qué hay de malo con el fragmento de código? Solo reflexiona un poco antes de seguir adelante.
El problema es realmente sutil. Si el padre invoca
thr_parent()
y luego examina el valor dedone
, verá que lo es0
y, por lo tanto, intentará conciliar el sueño. Pero justo antes de llamar a esperar para ir a dormir, el padre se interrumpe entre las líneas de 6-7 y el niño corre. El elemento secundario cambia la variable de estadodone
a1
señales, pero no hay ningún hilo esperando y, por lo tanto, no se despierta ningún hilo. Cuando el padre corre de nuevo, duerme para siempre, lo cual es realmente atroz.¿Qué pasa si se llevan a cabo mientras se adquieren las cerraduras individualmente?
fuente