variable pthread_cond_wait pthread cond c pthreads mutex

variable - Does pthread_cond_wait(& cond_t, & mutex); desbloquear y luego bloquear el mutex?



man pthread_cond_wait (3)

Cuando el primer hilo llama a pthread_cond_wait(&cond_t, &mutex); libera el mutex y espera hasta que la condición cond_t se cond_t como completa y mutex esté disponible.

Entonces, cuando se llama a pthread_cond_signal en el otro hilo, no "despierta" el hilo que aún espera. mutex debe desbloquearse primero, solo entonces existe la posibilidad de que el primer subproceso obtenga un bloqueo, lo que significa que "tras el retorno exitoso de pthread_cond_wait mutex se debe haber bloqueado y será propiedad del subproceso de llamada".

Estoy usando pthread_cond_wait(&cond_t, &mutex); en mi programa y me pregunto por qué esta función necesita como segundo parámetro una variable mutex.

¿El pthread_cond_wait() desbloquea el mutex al principio (inicio de la ejecución pthread_cond_wait() ) y luego se bloquea cuando termina (justo antes de dejar pthread_cond_wait() )?


Hay muchos textos sobre el tema de las variables de condición y su uso, así que no te aburriré con una tonelada de detalles desagradables. La razón por la que existen es permitirle notificar el cambio en un estado predicado . Los siguientes son fundamentales para comprender el uso adecuado de las variables de condición y su asociación mutex:

  • pthread_cond_wait() simultáneamente desbloquea el mutex y comienza a esperar que se marque la variable de condición. por lo tanto, siempre debe tener la propiedad del mutex antes de invocarlo.

  • pthread_cond_wait() regresa con el mutex bloqueado , por lo que debe desbloquear el mutex para permitir su uso en otro lugar cuando termine con él. Si el retorno ocurrió porque la variable de condición fue señalada o no no es relevante . Aún necesita verificar su predicado independientemente de la cuenta de potenciales despertares espurios .

  • El propósito del mutex no es proteger la variable de condición; es para proteger el predicado en el que se usa la variable de condición como mecanismo de señalización. Esta es la expresión más frecuente de las variables de condición pthread y sus mutexes. La variable de condición no necesita protección de exclusión mutua; los datos del predicado sí lo hacen . Piense en el predicado como un estado externo que está siendo monitoreado por los usuarios del par variable-condición / mutex.

Por ejemplo, un trozo de código trivial pero obviamente incorrecto para esperar un indicador booleano fSet :

bool fSet = false; int WaitForTrue() { while (!fSet) { sleep(n); } }

Debería ser obvio que el problema principal es el predicado, fSet , no está protegido en absoluto. Muchas cosas pueden salir mal aquí. Ej .: Desde el momento en que evalúa la condición de tiempo hasta el momento en que comienza a esperar (o girar, o lo que sea), el valor puede haber cambiado. Si esa notificación de cambio se pierde de alguna manera, estás esperando innecesariamente.

Podemos cambiar esto un poco para que al menos el predicado esté protegido de alguna manera. La exclusión mutua tanto en la modificación como en la evaluación del predicado se proporciona fácilmente con (qué más) un mutex.

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; bool fSet = false; int WaitForTrue() { pthread_mutex_lock(&mtx); while (!fSet) sleep(n); pthread_mutex_unlock(&mtx); }

Bueno, eso parece lo suficientemente simple. Ahora nunca evaluamos el predicado sin antes tener acceso exclusivo a él (bloqueando el mutex). Pero este sigue siendo un problema importante. Bloqueamos el mutex, pero nunca lo liberamos hasta que termina nuestro bucle . Si todos los demás fSet las reglas y esperan el bloqueo mutex antes de la evaluación o modificación de fSet , nunca podrán hacerlo hasta que abandonemos el mutex. El único "alguien" que puede hacer eso en este caso somos nosotros .

Entonces, ¿qué hay de agregar aún más capas a esto? esto funcionara?

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; bool fSet = false; int WaitForTrue() { pthread_mutex_lock(&mtx); while (!fSet) { pthread_mutex_unlock(&mtx); // XXXXX sleep(n); // YYYYY pthread_mutex_lock(&mtx); } pthread_mutex_unlock(&mtx); }

Bueno, sí, "funcionará", pero aún así no es mucho mejor. El período entre XXXXX y YYYYY no posee el mutex (lo cual está bien, ya que no estamos verificando ni modificando fSet todos modos). Pero en cualquier momento durante ese período, otro hilo puede (a) obtener el mutex, (b) modificar fSet y (c) liberar el mutex, y no sabremos nada al respecto hasta que terminemos nuestro sleep() , una vez de nuevo obtenga el bloqueo mutex, y vuelva para otra comprobación.

Tiene que haber una mejor manera. De alguna manera debería haber una forma de liberar el mutex y comenzar a esperar algún tipo de señal que nos indique que un cambio en el predicado puede haber ocurrido. Igualmente importante, cuando recibimos esa señal y volvemos a nuestro código, ya deberíamos poseer el bloqueo que nos otorga acceso para verificar los datos del predicado. Esto es exactamente lo que una variable de condición está diseñada para proporcionar.

La variable de condición en acción

Ingrese el par variable-condición + mutex. El mutex protege el acceso al cambio o comprobación del predicado, mientras que la variable de condición establece un sistema de monitorización de un cambio y, lo que es más importante, lo hace atómicamente (en todo caso, con la exclusión mutua de los predicados):

int WaitForPredicate() { // lock mutex (means:lock access to the predicate) pthread_mutex_lock(&mtx); // we can safely check this, since no one else should be // changing it unless they have the mutex, which they don''t // because we just locked it. while (!predicate) { // predicate not met, so begin waiting for notification // it has been changed *and* release access to change it // to anyone wanting to by unlatching the mutex, doing // both (start waiting and unlatching) atomically pthread_cond_wait(&cv,&mtx); // upon arriving here, the above returns with the mutex // latched (we own it). The predicate *may* be true, and // we''ll be looping around to see if it is, but we can // safely do so because we own the mutex coming out of // the cv-wait call. } // we still own the mutex here. further, we have assessed the // predicate is true (thus how we broke the loop). // take whatever action needed. // You *must* release the mutex before we leave. Remember, we // still own it even after the code above. pthread_mutex_unlock(&mtx); }

Para algún otro hilo para señalar el ciclo anterior, hay varias maneras de hacerlo, los dos más populares a continuación:

pthread_mutex_lock(&mtx); TODO: change predicate state here as needed. pthread_mutex_unlock(&mtx); pthread_cond_signal(&cv);

De otra manera...

pthread_mutex_lock(&mtx); TODO: change predicate state here as needed. pthread_cond_signal(&cv); pthread_mutex_unlock(&mtx);

Cada uno tiene un comportamiento intrínseco diferente y los invito a hacer algunos deberes sobre esas diferencias y determinar cuál es más apropiado para circunstancias específicas. El primero proporciona un mejor flujo de programas a expensas de la introducción de despertadores potencialmente injustificados. Este último reduce esos despertares pero a costa de una menor sinergia de contexto. O bien funcionará en nuestra muestra, y puede experimentar cómo cada uno afecta sus bucles de espera. De todos modos, una cosa es primordial, y ambos métodos cumplen este mandato:

Nunca cambie, ni verifique , la condición del predicado a menos que el mutex esté bloqueado . Alguna vez

Rosca de monitor simple

Este tipo de operación es común en un subproceso de monitor que actúa en una condición de predicado específica, que (sin verificación de errores) generalmente se ve así:

void* monitor_proc(void *pv) { // acquire mutex ownership // (which means we own change-control to the predicate) pthread_mutex_lock(&mtx); // heading into monitor loop, we own the predicate mutex while (true) { // safe to check; we own the mutex while (!predicate) pthread_cond_wait(&cv, &mtx); // TODO: the cv has been signalled. our predicate data should include // data to signal a break-state to exit this loop and finish the proc, // as well as data that we may check for other processing. } // we still own the mutex. remember to release it on exit pthread_mutex_unlock(&mtx); return pv; }

Un hilo de monitor más complejo

La modificación de este formulario básico para dar cuenta de un sistema de notificación que no requiere que mantenga el mutex enganchado una vez que haya recogido la notificación se vuelve un poco más complicado, pero no demasiado. A continuación se muestra un proceso de monitor que no mantiene el mutex enganchado durante el procesamiento regular una vez que hemos establecido que hemos sido atendidos (por así decirlo).

void* monitor_proc(void *pv) { // acquire mutex ownership // (which means we own change-control to the predicate) pthread_mutex_lock(&mtx); // heading into monitor loop, we own the predicate mutex while (true) { // check predicate while (!predicate) pthread_cond_wait(&cv, &mtx); // some state that is part of the predicate to // inform us we''re finished if (break-state) break; // TODO: perform latch-required work here. // unlatch the mutex to do our predicate-independant work. pthread_mutex_unlock(&mtx); // TODO: perform no-latch-required work here. // re-latch mutex prior to heading into wait pthread_mutex_lock(&mtx); } // we still own the mutex. remember to release it on exit pthread_mutex_unlock(&mtx); return pv; }

¿Dónde alguien usaría algo así? Bueno, supongamos que su "predicado" es el "estado" de una cola de trabajo, así como alguna bandera para indicarle que detenga el bucle y salga. Al recibir la notificación de que algo es "diferente", comprueba si debe continuar ejecutando su ciclo y, decidiendo que debe continuar, extraiga algunos datos de la cola. La modificación de la cola requiere que el mutex esté bloqueado (recuerde, su "estado" es parte de nuestro predicado). Una vez que hayamos extraído nuestros datos, los tenemos localmente y podemos procesarlos independientemente del estado de la cola, por lo que liberamos el mutex, hacemos lo nuestro, y luego solicitamos el mutex para la siguiente vuelta. Hay muchas maneras de codificar el concepto anterior, incluido el uso juicioso de pthread_cond_broadcast , etc. Pero la forma básica es, con suerte, comprensible.

Esto resultó ser considerablemente más largo de lo que esperaba, pero este es un gran obstáculo para las personas que aprenden la programación de subprocesos, y creo que vale la pena el tiempo / esfuerzo extra. Espero que hayas sacado algo de eso.


sí se desbloquea, espera a que se cumpla la condición y luego espera hasta que pueda adquirir el mutex pasado.