Sincronización de Hilos POSIX: Variables de Condición

En ocasiones un hilo debe esperar por el cambio de una variable y que ésta alcance un valor definido. Con lo estudiando hasta ahora, cuando esa variable es actualizada por otro hilo, el que espera debería hacerlo ocupando recursos de CPU (busy wait).
En principio podríamos pensar que teniendo posesión de un mutex, podríamos consultar por el cambio de la variable que esperamos. Una vez hecha la consulta deberíamos liberar el mutex. El problema es que estamos obligados a hacer un loop esperando por el cambio de la variable. Esto es una espera activa (o espera ocupada) la cual consume mucha CPU. Para evitar esto se requiere usar otro recurso de POSIX, éstas son las Variables de Condición (condition variable).

Las variables de condición son otro mecanismo de sincronización entre hilos. Las variables de condición usadas en conjunto con mutex permiten a un hilo esperar por la ocurrencia de una condición arbitraria. La espera es libre de carreras críticas.
Ejemplo caso común de espera ocupado (Busy wait). Si bien funciona, es ineficiente:
Visibles a ambas hebras:
  pthread_mutex_t   lock_de_mi_variable = PTHREAD_MUTEX_INITIALIZER;
  int variable = 0;

En una hebra:  /* Espera por un valor superior a LIMITE en variable */
   :
  int espere = 1;
  while (espere) {  /*Espera activa u ocupada, indeseable */
     pthread_ mutex_lock(&lock_de_mi_variable);
       if ( variable > LIMITE)  /* Alcanzó valor límite?*/
             espere=0;
      pthread_mutex_unlock(&lock_de_mi_variable);
  }
   /* hago lo que esperaba cuando variable > LIMITE */
  :
En Otra hebra:
   :
   pthread_ mutex_lock(&lock_de_mi_variable);
   variable++;
   pthread_mutex_unlock(&lock_de_mi_variable);
   :

POSIX entrega una forma mejor de resolver este problema. La idea es que si la condición no se cumple, en lugar de volver a consultar, quedarse bloqueado hasta que otra hebra nos informe del cambio de la valiable y así volver a consultar sólo cuando tenga sentido.  El recurso creado para esto se conoce como Variable de Condición. Éstas son representadas por el tipo de dato pthread_cond_t. Similar a mutex, podemos iniciar su valor con la constante PTHREAD_COND_INITIALIZER. En lugar de cargar los atributos por omisión, podemos usar el llamado a la función pthread_cond_init.

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *restrict cond);

Ambas retornan 0 si es OK, y un número de error en caso de falla.

Usamos la función pthread_cond_wait para esperar por un cambio que dé sentido a evaluar nuevamente la condición. Existe una variante para limitar la espera a un cierto tiempo.

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

Ambas retornan 0 si es OK, y un número de error en caso de falla.

Cuando un hilo invoca a pthread_cond_wait, éste libera el candado del mutex usado como argumento. Más adelante cuando es despertado, éste candado es devuelto a la hebra.

Con estas funciones el problema se resuelve de la siguiente manera:
Visibles a ambas hebras:
  pthread_mutex_t    lockDeMiVariable = PTHREAD_MUTEX_INITIALIZER;
  pthread_cond_t     cambioDeVariable = PTHREAD_COND_INITIALIZER;
  int variable = 0;

En una hebra: /* Espera por un valor superior a LIMITE en variable para poder proseguir */
   :
     pthread_ mutex_lock(&lockDeMiVariable);
     whilevariable <= LIMITE )  /* Debo seguir esperando mi condición !*/
         pthread_cond_wait(&cambioDeVariable,  &lockDeMiVariable); /*espera bloqueada*/
     pthread_mutex_unlock(&lockDeMiVariable);

  /* hago lo que esperaba para variable > LIMITE */
  :
En Otra hebra:
   :
   pthread_ mutex_lock(&lockDeMiVariable);
   variable++;
   pthread_mutex_unlock(&lockDeMiVariable);
   pthread_cond_signal(&cambioDeVariable);
   :

Hay dos funciones para notificar a hebras que una condición ha cambiado. La función pthread_cond_signal  despertará a una hebra que espera con la misma variable de condición. La función pthread_cond_broadcast despierta a todas la hebras esperando por la condición.

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *restrict cond);

int pthread_cond_broadcast(pthread_cond_t *restrict cond);

Ambas retornan 0 si es OK, y un número de error en caso de falla.

Ejemplo de uso de hebras y variables condicionales: Cree un programa que envíe a pantalla todo lo que se ingrese por teclado. Para probar este programa y poder distinguir entre lo enviado por el programa y el eco automático del teclado, usted lo puede correr usando:
$ mkfifo myfifo
Luego en una consola pone:
$ cat myfifo
y en otra pone:
$ programaEco > myfifo

Ahora deseamos limitar a 1 caracter por segundo lo enviado a pantalla. Usaremos aquí el algoritmo "leaky bucket" con tamaño de balde de 10 caracteres. Usted puede ejecutar el programa como el previo y verá cómo los caracteres aparecen de a uno a la vez y con una tasa de 1 caracter/s.
Para controlar la tasa de tiempo, usted puede usar un timer iterativo o simplemente sleep(1). Note que sleep señala que sólo hace dormir a la hebra que lo invoca. Usted puede estudiar ambas soluciones, ver:
leakyBucket_with_sleep.c: Solución simple que hace uso de sleep para controlar el goteo del balde.
leakyBucket.c :  Solución similar a la previa pero más precisa y compleja. Hace uso de SIGALRM, la cual requiere manejo de señales en hilos.