UC3M

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

Arquitectura de Sistemas

Septiembre 2015 - Enero 2016

11.4. Variables de condición

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.

11.4.1. Variable de condición: Motivación

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.