UC3M

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2017 - Enero 2018

11.3.2. Cerrojos: Ejemplo

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.