¿Por qué pthread_cond_wait tiene despertadores espurios?

145

Para citar la página del manual:

Cuando se usan variables de condición, siempre hay un predicado booleano que involucra variables compartidas asociadas con cada espera de condición que es verdadero si el hilo debe continuar. Pueden ocurrir activaciones espurias de las funciones pthread_cond_timedwait () o pthread_cond_wait (). Dado que el retorno de pthread_cond_timedwait () o pthread_cond_wait () no implica nada sobre el valor de este predicado, el predicado debe ser reevaluado en dicho retorno.

Por lo tanto, pthread_cond_waitpuede regresar incluso si no lo ha señalado. A primera vista, al menos, eso parece bastante atroz. Sería como una función que devolvió aleatoriamente el valor incorrecto o regresó aleatoriamente antes de que realmente alcanzara una declaración de retorno adecuada. Parece un error importante. Pero el hecho de que eligieron documentar esto en la página del manual en lugar de corregirlo parece indicar que hay una razón legítima por la que pthread_cond_waittermina despertando espuriosamente. Presumiblemente, hay algo intrínseco en su funcionamiento que hace que no se pueda evitar. La pregunta es qué.

¿ Por quépthread_cond_wait vuelve espuriamente? ¿Por qué no puede garantizar que solo se despertará cuando se haya señalado correctamente? ¿Alguien puede explicar la razón de su comportamiento espurio?

Jonathan M Davis
fuente
55
Me imagino que tiene algo que ver con regresar cada vez que el proceso capta una señal. La mayoría de los * nixes no reinician una llamada de bloqueo después de que una señal la interrumpe; simplemente establecen / devuelven un código de error que dice que ocurrió una señal.
cHao
1
@ cHao: aunque tenga en cuenta que debido a que las variables de condición tienen otras razones para despertar espurias de todos modos, manejar una señal no es un error para pthread_cond_(timed)wait: "Si se entrega una señal ... el hilo se reanuda esperando la variable de condición como si fuera no se interrumpe, o devolverá cero debido a la activación espuria ". Otras funciones de bloqueo indican EINTRcuando se interrumpe por una señal (p read. Ej. ), O se requiere que se reanuden (p pthread_mutex_lock. Ej .). Entonces, si no hubiera otras razones para el despertar espurio, pthread_cond_waitpodría haberse definido como cualquiera de esos.
Steve Jessop
44
Un artículo relacionado en Wikipedia:
Spurious
3
Vladimir Prus útil : Despiertos espurios .
iammilind
Muchas funciones no pueden realizar completamente su trabajo por completo (E / S interrumpidas) y las funciones de observación pueden recibir eventos que no son como un cambio en un directorio donde el cambio fue cancelado o revertido. ¿Cuál es el problema?
curioso

Respuestas:

77

David R. Butenhof da la siguiente explicación en "Programación con hilos POSIX" (p. 80):

Las activaciones espurias pueden sonar extrañas, pero en algunos sistemas multiprocesador, hacer que la activación de la condición sea completamente predecible podría ralentizar sustancialmente todas las operaciones variables de la condición.

En la siguiente discusión de comp.programming.threads , él amplía el pensamiento detrás del diseño:

Patrick Doyle escribió: 
> En el artículo, Tom Payne escribió: 
>> Kaz Kylheku escribió: 
>>: Esto es así porque las implementaciones a veces no pueden evitar insertar 
>>: estos espurios despertares; Puede ser costoso prevenirlos.

>> ¿Pero por qué? Porque esto es tan difícil? Por ejemplo, ¿estamos hablando de
>> situaciones donde se agota el tiempo de espera justo cuando llega una señal? 

> Sabes, me pregunto si los diseñadores de pthreads usaron una lógica como esta: 
> los usuarios de variables de condición tienen que verificar la condición al salir de todos modos, 
> por lo que no les aplicaremos ninguna carga adicional si permitimos 
> despertares espurios; y dado que es concebible que permitir espurias
> wakeups podría acelerar la implementación, solo puede ayudar si 
> permitirlos. 

> Es posible que no hayan tenido en mente ninguna implementación en particular. 

En realidad no estás muy lejos, excepto que no lo empujaste lo suficiente. 

La intención era forzar el código correcto / robusto al requerir bucles de predicados. Esto era
impulsado por el contingente académico probablemente correcto entre los "hilos centrales" en 
el grupo de trabajo, aunque no creo que nadie esté realmente en desacuerdo con la intención 
una vez que entendieron lo que significaba. 

Seguimos esa intención con varios niveles de justificación. El primero fue que
El uso "religioso" de un bucle protege la aplicación contra su propio imperfecto. 
prácticas de codificación La segunda fue que no era difícil imaginar abstractamente
máquinas y código de implementación que podrían explotar este requisito para mejorar 
El rendimiento de las operaciones de espera de condición promedio a través de la optimización de 
Mecanismos de sincronización. 
/ ------------------ [David.Buten ... @ compaq.com] ------------------ \ 
El | Compaq Computer Corporation POSIX Thread Architect |
El | Mi libro: http://www.awl.com/cseng/titles/0-201-63392-2/ |
\ ----- [http://home.earthlink.net/~anneart/family/dave.html] ----- / 

NPE
fuente
22
Básicamente esto no dice nada. Aquí no se da ninguna explicación más que el pensamiento inicial de que "puede acelerar las cosas", pero nadie sabe cómo hacerlo o si lo hace.
Bogdan Ionitza
107

Hay al menos dos cosas que podría significar 'despertar espurio':

  • Un hilo bloqueado en pthread_cond_waitpuede volver de la llamada a pesar de que no hay ninguna llamada a pthread_call_signalo pthread_cond_broadcasten la condición haya ocurrido.
  • Un hilo bloqueado en pthread_cond_waitrendimientos debido a una llamada a pthread_cond_signalo pthread_cond_broadcast, sin embargo después de readquirir el mutex el predicado subyacente se encuentra que ya no es cierto.

Pero el último caso puede ocurrir incluso si la implementación de la variable de condición no permite el primer caso. Considere una cola de consumidor productor y tres hilos.

  • El subproceso 1 acaba de retirar un elemento y libera el mutex, y la cola ahora está vacía. El hilo está haciendo lo que hace con el elemento que adquirió en alguna CPU.
  • El subproceso 2 intenta eliminar un elemento, pero encuentra que la cola está vacía cuando se verifica bajo el mutex, llama pthread_cond_wait y bloques en la llamada en espera de señal / transmisión.
  • El subproceso 3 obtiene el mutex, inserta un nuevo elemento en la cola, notifica la variable de condición y libera el bloqueo.
  • En respuesta a la notificación del subproceso 3, el subproceso 2, que estaba esperando la condición, está programado para ejecutarse.
  • Sin embargo, antes de que el hilo 2 logre subir a la CPU y tomar el bloqueo de la cola, el hilo 1 completa su tarea actual y regresa a la cola para más trabajo. Obtiene el bloqueo de la cola, comprueba el predicado y descubre que hay trabajo en la cola. Se procede a retirar el elemento que insertó el subproceso 3, libera el bloqueo y hace lo que sea que haga con el elemento que el subproceso 3 puso en cola.
  • El subproceso 2 ahora se conecta a una CPU y obtiene el bloqueo, pero cuando comprueba el predicado, descubre que la cola está vacía. El hilo 1 'robó' el artículo, por lo que la activación parece ser espuria. El subproceso 2 debe esperar la condición nuevamente.

Por lo tanto, dado que siempre necesita verificar el predicado bajo un bucle, no hay diferencia si las variables de condición subyacentes pueden tener otros tipos de activaciones espurias.

acm
fuente
23
si. Esencialmente, esto es lo que sucede cuando se usa un evento en lugar de un mecanismo de sincronización con un conteo. Lamentablemente, parece que los semáforos POSIX, (en Linux de todos modos), también están sujetos a reactivaciones espurias. Me parece un poco extraño que una falla de funcionalidad fundamental de las primitivas de sincronización se acepte como 'normal' y deba solucionarse a nivel de usuario :( Presumiblemente, los desarrolladores estarían en conflicto si se documentara una llamada al sistema con una sección 'Espurias segfault' o, tal vez, 'Espurias conectando a la URL incorrecta' o 'Apertura espuria del archivo incorrecto'.
Martin James
2
El escenario más común de una "activación espuria" es muy probablemente el efecto secundario de una llamada a pthread_cond_broadcast (). Digamos que tiene un grupo de 5 hilos, dos se despiertan para la transmisión y hacen el trabajo. Los otros tres se despiertan y encuentran que el trabajo ya está hecho. Los sistemas multiprocesador también pueden dar como resultado una señal condicional que despierta múltiples subprocesos por accidente. El código simplemente vuelve a verificar el predicado, ve un estado no válido y vuelve a dormir. En cualquier caso, verificar el predicado resuelve el problema. OMI, en general, los usuarios no deberían usar mutexes y condicionales POSIX sin procesar.
CubicleSoft
1
@MartinJames - ¿Qué tal el clásico EINTR "espurio"? Estoy de acuerdo en que probar constantemente para EINTR en un bucle es un poco molesto y hace que el código sea bastante feo, pero los desarrolladores lo hacen de todos modos para evitar roturas aleatorias.
CubicleSoft
2
@Yola No, no puede, porque se supone que debes bloquear un mutex alrededor pthread_cond_signal/broadcasty no podrás hacerlo, hasta que el mutex se desbloquee llamando pthread_cond_wait.
a3f
1
El ejemplo de esta respuesta es muy realista y estoy de acuerdo en que verificar predicados es una buena idea. Sin embargo, no podría solucionarse de la misma manera si da el paso problemático "el hilo 1 completa su tarea actual, y vuelve a la cola para más trabajo" y lo reemplaza por "el hilo 1 completa su tarea actual, y vuelve a esperar en la variable de condición "? Eso eliminaría el modo de falla descrito en la respuesta, y estoy bastante seguro de que corregirá el código, en ausencia de despertadores espurios . ¿Existe alguna implementación real que produzca despertadores espurios en la práctica?
Quuxplusone
7

La sección "Múltiples despertares por señal de condición" en pthread_cond_signal tiene una implementación de ejemplo de pthread_cond_wait y pthread_cond_signal que involucra despertadores espurios.

Jingguo Yao
fuente
2
Creo que esta respuesta es incorrecta, hasta donde llega. La implementación de muestra en esa página tiene una implementación de "notificar a uno" que es equivalente a "notificar a todos"; pero no parece generar despertos realmente espurios . La única manera de que un hilo se despierte es mediante otro hilo que invoque "notificar a todos", o por algún otro hilo que invoque la cosa etiquetada como "notificar a uno", que realmente es "notificar a todos".
Quuxplusone
5

Si bien no creo que se haya considerado en el momento del diseño, aquí hay una razón técnica real: en combinación con la cancelación de subprocesos, hay condiciones bajo las cuales puede ser absolutamente necesario tomar la opción de despertar "espuriosamente", al menos a menos que usted estamos dispuestos a imponer restricciones muy muy fuertes sobre qué tipo de estrategias de implementación son posibles.

El problema clave es que, si un hilo actúa sobre la cancelación mientras está bloqueado pthread_cond_wait, los efectos secundarios deben ser como si no consumiera ninguna señal en la variable de condición. Sin embargo, es difícil (y altamente restrictivo) asegurarse de que no haya consumido una señal cuando comience a actuar en la cancelación, y en esta etapa puede ser imposible "volver a publicar" la señal en la variable de condición, ya que puede estar en una situación en la que la persona que llama pthread_cond_signalya está justificada por haber destruido el condvar y haber liberado la memoria en la que residía.

La asignación para la estela espuria te da una salida fácil. En lugar de continuar actuando sobre la cancelación cuando llega mientras está bloqueado en una variable de condición, si ya ha consumido una señal (o si desea ser perezoso, pase lo que pase), puede declarar que ha ocurrido una estela espuria. y volver con exito. Esto no interfiere en absoluto con la operación de cancelación, porque una persona que llama correctamente simplemente actuará en la cancelación pendiente la próxima vez que realice un bucle y pthread_cond_waitvuelva a llamar .

R .. GitHub DEJA DE AYUDAR AL HIELO
fuente