La principal limitación que tienen cerrojos (mutex) es que en aplicaciones que tengan que
esperar a que un bloque acabe con su ejecución para continuar. Eso acaba provocando que a efectos prácticos
un hilo que tenga que esperar por una señal de otro tenga que esperar mediante bucles activos que recomprueben
el sistema para poder continuar. Ofreciendo un mecanismo de bloqueo seguro y eficiente, aparece la variable de
condición pthread_cond
. Esta variable de condición posee dos funciones asociadas que permiten
que se decida esperar bloqueado (con pthread_cond_wait
), que será señalizado desde otro con hilo
diferente con pthread_cond_signal
.
Al igual que en el caso anterior del mutex, la variable de codición se puede configurar.
El siguiente trozo de código muestra un programa donde dos hilos tienen que esperar a que otro acabe para continuar con su ejecución:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | // compile with $ gcc -Wall -g *.c -pthread -o program // run with ./program // check with valgrind --tool=helgrind ./program #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex; void* thread_run(void* data_exit) { int i_tmp=-1; sleep(2); printf("[TH_ID:%ld]: Hello from the thread \n", pthread_self()); printf("[TH_ID:%ld]: Reading %i \n", pthread_self(),(*(int*)data_exit)); i_tmp=(*(int*)data_exit); for(;i_tmp==0;) { pthread_mutex_lock(&mutex); i_tmp=(*(int*)data_exit); if(i_tmp!=0) { (*(int*)data_exit)--; pthread_mutex_unlock(&mutex); return data_exit; } else { pthread_mutex_unlock(&mutex); printf("[TH_ID:%ld]: sleeping 2 seconds, %i \n", pthread_self(),i_tmp); sleep(1); } } printf("[TH_ID:%ld]: Writing %i \n", pthread_self(),(*(int*)data_exit)); printf("[TH_ID: %ld]: To exit...............\n",pthread_self()); return data_exit; } int main() { int i; pthread_t thread[2]; int i_salir=0; //Number of threads that may exit int thread_rc; if(pthread_mutex_init(&mutex,NULL)!=0) return -1; for (i=0;i<2; i++) { printf("[MAIN:%ld]: Starting............ \n",pthread_self()); if ((thread_rc=pthread_create(&thread[i],NULL,thread_run,&i_salir))!=0) { printf("Error creating the thread. Code %i",thread_rc); return -1; } } sleep(1); printf("[MAIN:%ld]: Thread allocated \n",pthread_self()); sleep(3); //Say to the thread you should exit pthread_mutex_lock(&mutex); i_salir=2; pthread_mutex_unlock(&mutex); int *ptr_output_data; for ( i=0;i<2; i++) { pthread_join(thread[i],(void **)&ptr_output_data); } pthread_mutex_destroy(&mutex); printf("[MAIN:%ld]: Thread pending to return %d \n",pthread_self(), *ptr_output_data); return 0; } |
La solución propuesta se basa en las siguientes ideas principales:
La función main
crea las dos hebras que tras realizar cierto trabajo esperando a que terminen su ejecución.
La señal de terminar se hace a través de una variable inicializada a cero. Cuando esa variable se pone a un número diferente de cero (en este caso a dos) los hilos salen, decrementándose así el valor de esta variable.
Los hilos que tienen que salir comprueban periódicamente el valor de la variable de salir.
Lo que acaba generando una salida donde los hilos creados periodicamente reimprime la siguiente secuencia de mensajes:
[MAIN:2]: Starting............ [MAIN:2]: Starting............ [MAIN:2]: Thread allocated [TH_ID:0]: Hello from the thread [TH_ID:0]: Reading 0 [TH_ID:0]: sleeping 2 seconds, 0 [TH_ID:6]: Hello from the thread [TH_ID:6]: Reading 0 [TH_ID:6]: sleeping 2 seconds, 0 [TH_ID:0]: sleeping 2 seconds, 0 ... [MAIN:2]: Thread pending to return 0
El código también ilustra uno de los principales problemas que ataja la variable de condición:la espera activa (busy waiting en inglés). Este es el de tener que recomprobar la salida de forma periódica, el valor de la variable pasada como parámetro de la función para recibir una instrucción de otro hilo que le deje continuar. Eso en principio es ineficiente pues obliga a que el hilo recompruebe periódicamente dicha variable. La variable de condición provee una solución más elegante y eficiente.