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.