La forma en que se puede utilizar un cerrojo pthread_mutex
para resolver es problema es hacer
que todos los hilos para realizar la operación que no se puede partir ("leer dato, modificar y escribir") utilicen el mismo
cerrojo. El cerrojo se cogería antes de ejecutar esta sección crítica y se liberaría más tarde, después de modificar el dato.
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 | // 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) { int i_data; sleep(2); printf("[TH_ID:%ld]: Hello from the thread \n", pthread_self()); pthread_mutex_lock(&mutex); printf("[TH_ID:%ld]: Reading %i \n", pthread_self(),(*(int*)data)); i_data=(*(int*)data); sleep(1); //It should be removed i_data++; (*(int*)data)=i_data; printf("[TH_ID:%ld]: Writing %i \n", pthread_self(),(*(int*)data)); pthread_mutex_unlock(&mutex); printf("[TH_ID: %ld]: To exit...............\n",pthread_self()); return data; } int main() { int i; pthread_t thread[2]; int data=0; 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,&data))!=0) { printf("Error creating the thread. Code %i",thread_rc); return -1; } } sleep(1); printf("[MAIN:%ld]: Thread allocated \n",pthread_self()); 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 returns %d \n",pthread_self(), *ptr_output_data); return 0; } |
En este ejemplo se puede ver que la ejecución de los hilos siempre devuelve el mismo resultado en el terminal:
[MAIN:1]: Starting............ [MAIN:2]: Starting............ [MAIN:2]: Thread allocated [TH_ID:0]: Hello from the thread [TH_ID:0]: Reading 0 [TH_ID:0]: Writing 1 [TH_ID:0]: To exit............... [TH_ID:1]: Hello from the thread [TH_ID:1]: Reading 1 [TH_ID:1]: Writing 2 [TH_ID:1]: To exit............... [MAIN:2]: Thread returns 2
La lista de ejecuciones que provoca la utilización del cerrojo que hace que la lectura y escritura de datos de los diferentes hilos no se realice concurrentemente incluye las siguientes ejecuciones:
H_0 lock(m), H_1 lock(m), H_0 lee_(0), H_0 esc_(1), H_0 unlock(m), H_1 lee_(1), H_1 esc_(2), H_1 unlock(m)
H_1 lock(m), H_0 lock(m), H_1 lee_(0), H_1 esc_(1), H_1 unlock(m), H_0 lee_(1), H_0 esc_(2), H_0 unlock(m)
Y una de las que nunca se debería de producir, dado que la evitan los cerrojos, es la siguiente:
H_0 lock(m), H_1 lock(m), H_0 lee_(0), H_1 lee_(0), H_0 esc_(1), H_1 esc_(1), H_0 unlock(m), H_1 unlock(m)
Esta situación nunca se produce porque el cerrojo impide que dos hilos tomen el cerrojo al mismo tiempo.