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.
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.
ResponderEliminarBuenas! Cuando saque un rato lo reviso y te comento. Gracias por darte cuenta del fallo =)
Eliminaracabo 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.
EliminarEste ejemplo funciona con "n" hilos para copiar y "m" hilos para escribir, osea 3 hilos leen y 4 hilos escriben por ejemplo.
ResponderEliminarSí,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