¿Por qué pthread_cond_wait tiene activaciones falsas?

Resuelto Jonathan M Davis asked hace 12 años • 6 respuestas

Para citar la página de manual:

Cuando se utilizan variables de condición, siempre hay un predicado booleano que involucra variables compartidas asociadas con cada condición de espera que es verdadera si el hilo debe continuar. Pueden producirse reactivaciones 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 reevaluarse tras dicho retorno.

Por lo tanto, pthread_cond_waitpuede regresar incluso si no lo ha indicado. Al menos a primera vista, esto parece bastante atroz. Sería como una función que devolviera aleatoriamente un valor incorrecto o que devolviera aleatoriamente antes de alcanzar una declaración de devolución adecuada. Parece un error importante. Pero el hecho de que hayan elegido documentar esto en la página de manual en lugar de arreglarlo parece indicar que hay una razón legítima por la que pthread_cond_waittermina despertando de forma espuria. Presumiblemente, hay algo intrínseco en cómo funciona que hace que no se pueda evitar. La pregunta es qué.

¿ Por qué regresa pthread_cond_waitespuriamente? ¿Por qué no puede garantizar que sólo se despertará cuando haya recibido la señal adecuada? ¿Alguien puede explicar el motivo de su comportamiento espurio?

Jonathan M Davis avatar Dec 22 '11 01:12 Jonathan M Davis
Aceptado

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

  • Un hilo bloqueado pthread_cond_waitpuede regresar de la llamada aunque no se haya producido ninguna llamada pthread_cond_signala pthread_cond_broadcastla condición.
  • Un hilo bloqueado pthread_cond_waitregresa debido a una llamada a pthread_cond_signalo pthread_cond_broadcast; sin embargo, después de volver a adquirir el mutex, se descubre que el predicado subyacente ya no es verdadero.

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 productor-consumidor y tres subprocesos.

  • El subproceso 1 acaba de quitar de la cola un elemento y liberó el mutex, y la cola ahora está vacía. El hilo está haciendo lo que sea que haga con el elemento que adquirió en alguna CPU.
  • El subproceso 2 intenta retirar un elemento de la cola, pero encuentra que la cola está vacía cuando se verifica bajo el mutex, llamadas pthread_cond_waity 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, se programa la ejecución del subproceso 2, que estaba esperando la condición.
  • Sin embargo, antes de que el subproceso 2 logre acceder a la CPU y tomar el bloqueo de la cola, el subproceso 1 completa su tarea actual y regresa a la cola para realizar más trabajo. Obtiene el bloqueo de la cola, verifica el predicado y descubre que hay trabajo en la cola. Procede a retirar de la cola el elemento que el hilo 3 insertó, libera el bloqueo y hace lo que sea con el elemento que el hilo 3 puso en cola.
  • El subproceso 2 ahora accede a una CPU y obtiene el bloqueo, pero cuando verifica el predicado, descubre que la cola está vacía. El hilo 1 'robó' el artículo, por lo que el despertar parece ser falso. El hilo 2 necesita esperar la condición nuevamente.

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

acm avatar Dec 21 '2011 19:12 acm

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

Los despertares espurios pueden parecer extraños, pero en algunos sistemas multiprocesador, hacer que el despertar de condiciones sea completamente predecible podría ralentizar sustancialmente todas las operaciones de variables de condición.

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

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

> >Pero ¿por qué? Porque esto es tan difícil? Por ejemplo, ¿estamos hablando de
> >situaciones en las que 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 deben verificar la condición al salir de todos modos,
> por lo que no les impondremos ninguna carga adicional si permitimos
> despertares espurios; y dado que es concebible que permitir
> los despertadores podrían hacer que una implementación sea más rápida, solo puede ayudar si
> permitirles.

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

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

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

Seguimos esa intención con varios niveles de justificación. La primera fue que
El uso "religioso" de un bucle protege la aplicación contra su propia imperfección.
prácticas de codificación. La segunda era que no era difícil imaginar de manera abstracta
máquinas y código de implementación que podrían aprovechar este requisito para mejorar
el rendimiento de las operaciones de espera en condiciones promedio mediante la optimización del
Mecanismos de sincronización.
/------------------[ [email protected] ]------------------\
| Compaq Computer Corporation Arquitecto de subprocesos POSIX |
| Mi libro: http://www.awl.com/cseng/titles/0-201-63392-2/ |
\-----[ http://home.earthlink.net/~anneart/family/dave.html ]-----/

NPE avatar Dec 21 '2011 18:12 NPE

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 despertares espurios.

Jingguo Yao avatar Jul 17 '2012 06:07 Jingguo Yao

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 del hilo, existen condiciones bajo las cuales tomar la opción de despertar "falsamente" puede ser absolutamente necesario, al menos a menos que 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 ya una señal cuando comience a actuar sobre 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 liberado la memoria en la que residía.

La concesión de estelas espurias le 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 es posible que ya haya consumido una señal (o si quiere ser perezoso, pase lo que pase), puede declarar que ha ocurrido una estela espuria. y regresar con éxito. Esto no interfiere en absoluto con la operación de cancelación, porque una persona que llama correctamente simplemente actuará sobre la cancelación pendiente la próxima vez que realice un bucle y pthread_cond_waitvuelva a llamar.