jueves, 2 de febrero de 2012

Concurrencia - Variables Condicionales


Esta entrada es una continuación de la entrada de mutex y de la entrada acerca de la creación de hilos. En esta entrada hablaré acerca de las variables condicionales. Estas deben usarse conjuntamente a los mutex. Pronto veremos de qué forma nos pueden ser útiles las variables condicionales.

Para comenzar con ellas veremos qué es una variable condicional: “Son variables de sincronización asociadas a un mutex”.  Estas variables nos ayudaran a sincronizar nuestros bloqueos entre hilos. Por ejemplo realizamos un bloqueo pero tenemos que esperar a que otro recurso realice una acción, con estas variables podemos esperar esta acción. Estas variables pueden realizar principalmente dos acciones: wait y signal. Las operaciones conviene realizarlas entre lock y unlock de un mutex. Los métodos que podemos usar con estas variables son:
  •  int pthread_cond_init(pthread_cond_t*cond, pthread_condattr_t*attr);
o   Inicializa la variable condicional.
  •  int pthread cond destroy(pthread_cond_t *cond);
o   Destruye la variable condicional.
  •  int pthread_cond_wait(pthread_cond_t*cond, pthread_mutex_t*mutex);
o   Mediante este método suspendemos el hilo en el que se ejecuta y liberamos el mutex. El proceso queda suspendido hasta que recibe una señal de activación. Una vez que se activa se vuelve a luchar por el control del mutex.
  • int pthread_cond_signal(pthread_cond_t *cond);
o Se reanuda la ejecución de uno o más hilos que estén esperando por la variable condicional (cond).
o   En caso de no haber ningún proceso esperando no ocurre nada.
  • int pthread_cond_broadcast(pthread_cond_t *cond);
o   Se reanudan todos los hilos que estaban suspendidos por la variable condicional (cond).

Para entender esto mejor veremos un ejemplo con un código. Para este ejemplo resolveremos el problema del productor-consumidor. Este problema viene dado por un hilo productor  que genera datos (véase en este caso un array) mientras que un hilo consume estos datos (los eliminan del array). Veamos el código:

 
#define MAX_BUFFER 1024 /* tamanio del buffer */
#define DATOS_A_PRODUCIR 100000 /* datos que se desean producir */


pthread_mutex_t mutex; /* mutex de acceso al buffer compartido */
pthread_cond_t no_lleno; /* Variable condicional que controla el llenado del buffer */
pthread_cond_t no_vacio; /* Variable condicional que  controla el vaciado del buffer */
int n_elementos; /* numero de elementos en el buffer */
int buffer[MAX_BUFFER]; /* buffer comun */

void Productor(void) { /* codigo del productor */
 int dato, i ,pos = 0;
 for(i=0; i < DATOS_A_PRODUCIR; i++ ) {
  dato = i; /* producir dato */
  pthread_mutex_lock(&mutex); /* acceder al buffer */
  while (n_elementos == MAX_BUFFER){ /* si buffer lleno */
   pthread_cond_wait(&no_lleno, &mutex); /* se bloquea */
  }
  buffer[pos] = i;
  pos = (pos + 1) % MAX_BUFFER;/*calculamos la posicion del siguiente elemento en el buffer*/
  n_elementos ++;
  pthread_cond_signal(&no_vacio); /* si hemos creado un dato el buffer no estará vacio*/
  pthread_mutex_unlock(&mutex);/*liberamos el mutex*/
 }
 pthread_exit(0);
}

void Consumidor(void){
/* codigo del sonsumidor */
 int dato, i ,pos = 0;
 for(i=0; i < DATOS_A_PRODUCIR; i++ ) {
  pthread_mutex_lock(&mutex); /* acceder al buffer */
  while (n_elementos == 0){ /* si buffer vacio */
   pthread_cond_wait(&no_vacio, &mutex); /* se bloquea */
  }
  dato = buffer[pos];
  pos = (pos + 1) % MAX_BUFFER;/*calculamos la posicion del siguiente elemento en el buffer*/
  n_elementos --;
  pthread_cond_signal(&no_lleno); /* buffer no lleno */
  pthread_mutex_unlock(&mutex);
  printf("Consume %d \n", dato); /* consume dato */
 }
 pthread_exit(0);
}

int main(int argc, char *argv[]){
 pthread_t th1, th2;
 pthread_mutex_init(&mutex, NULL);
 pthread_cond_init(&no_lleno, NULL);
 pthread_cond_init(&no_vacio, NULL);
 pthread_create(&th1, NULL, (void *)&Productor, NULL);
 pthread_create(&th2, NULL, (void *)&Consumidor, NULL);
 pthread_join(th1, NULL);
 pthread_join(th2, NULL);
 pthread_mutex_destroy(&mutex);
 pthread_cond_destroy(&no_lleno);
 pthread_cond_destroy(&no_vacio);
 exit(0);
}

Podemos ver cómo se va llenando el array. Una vez llegada a la última posición vuelve al inicio. Los productores comprueban que el array no esté lleno, en caso de estarlo espera. Por otro lado los consumidores obtienen los datos del array y en caso de que el array este vacío esperan a que algún productor escriba algo. Estas variables condicionales nos han ayudado a comprobar si están llenos o vacios y esperar en cada caso a que otro hilo le mande una señal.

Si tenéis alguna duda no dudéis en comentar. Espero que os haya resultado de ayuda esta entrada para poder comprender un poco mejor la sincronización entre hilos.

5 comentarios:

  1. Buenas! al hacer el programa en vez de empezar a coger datos empieza a extraer y se queda pillado ese hilo. He puesto unos printfs para comprobarlo al principio y al final de la inserccion y extraccion del dato.

    ResponderEliminar
    Respuestas
    1. Buenas! Cuando saque un rato lo reviso y te comento. Gracias por darte cuenta del fallo =)

      Eliminar
    2. acabo de realizar este ejemplo de nuevo en DEBIAN. He usado como compilador GCC. Lo he ejecutado varias veces y todas funcionan correctamente. Es posible que el problema venga del compilador o del Sistema Operativo. De momento tan solo tengo disponible esta máquina para realizar pruebas. Cuando tenga otra máquina lo vuelvo a probar.

      Eliminar
  2. Este ejemplo funciona con "n" hilos para copiar y "m" hilos para escribir, osea 3 hilos leen y 4 hilos escriben por ejemplo.

    ResponderEliminar
    Respuestas
    1. Sí,podrías aplicarlo a n hilos para producir y m hilos para consumir. Hace tiempo que no me enfrento a un problema de productor/consumidor mediante mutex. Sin embargo, teóricamente esta solución puede realizar n productores y m consumidores.

      Eliminar