UC3M

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

Arquitectura de Sistemas

Septiembre 2017 - Enero 2018

11.2. Hilos

Empecemos explorando las interfaces de creación de hilos (también llamados por algunos autores hebras). En su forma más básica, dicha interfaz ofrece la posibilidad de crear hilos y esperar a que estos terminen. Los hilos reciben parámetros cuando son creados y devuelven un resultado una vez el código ha finalizado. Este tipo de de relación es similar a la que tiene un programa que ha sido lanzado desde el terminal con su creador.

El siguiente trozo de código muestra un ejemplo de una aplicación con un hilo que recibe por parámetro de entrada un número, lo incrementa en una unidad y devuelve dicho resultado al hilo que lo ha invocado, que espera el resultado y lo imprime. (Nota: los sleeps son didácticos y sirven para controlar el tiempo que tarda en ejecutarse una hebra).

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
// 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>

void* thread_run(void* data)
{ sleep(2); 
  printf("[TH_1:%ld]: Hello from the thread \n", pthread_self());
  sleep(1);
  (*(int*)data)++;
  printf("[TH_1: %ld]: To exit...............\n",pthread_self());
  pthread_exit(data);
}

int main()
{
  pthread_t thread;
  int data=0;
  int thread_rc;
  printf("[MAIN:%ld]: Starting............ \n",pthread_self());
  if ((thread_rc=pthread_create(&thread,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;
  pthread_join(thread,(void **)&ptr_output_data);
  printf("[MAIN:%ld]: Thread returns %d \n",pthread_self(), *ptr_output_data);
  return 0;
} 

El código muestra el tipo de interacción que se puede realizar con los hilos POSIX. Veamos algunos puntos de interés del código:

  • Para poder utilizar la interfaz de los hilos es necesario incluir la cabecera pthread.h. Además, a la hora de compilar hay que enlazar el código con la opción -lpthread.

  • La creación de un hilo se hace mediante pthread_create. A partir de este punto, si la función no produce error, hay dos hilos de ejecución: el del programa invocante y otro cuyo nombre de función se pasa por parámetro y en nuestro caso se corresponde con thread_run. Dicha función recibe un puntero a datos y devuelve otro. Típicamente, el hilo invocante usa el último parametro de hilo para enviar datos de entrada al nuevo hilo.

  • El resultado del hilo se devuelve cuando la función con la que se crea el hilo finaliza. El hilo devuelve un puntero cuyos resultados se pueden recoger, más tarde, con pthread_join desde el hilo padre que lo ha creado.

Cuando se compila el código y se ejecuta, se puede ver como cada hilo genera diferentes mensajes de forma independiente. Eso es típico de la programación concurrente pues cada hebra trabaja a una velocidad diferente.

$ gcc pthreads_create_join_main.c  -pthread -o pthreads_create_join_main
$ ./pthreads_create_join_main
[MAIN:-1218705728]: Starting............ 
[MAIN:-1218705728]: Thread allocated 
[TH_1:-1218708672]: Hello from the thread 
[TH_1:-1218708672]: To exit...............
[MAIN:-1218705728]: Thread returns 1 

11.2.1. Un hilo, una ejecución concurrente

Una vez creados, cada hilo progresa de forma independiente al resto. Esto provoca que cada uno de los hilos pueda potencialmente viajar a una velocidad distinta, ejecutándose "concurrentemente" con el resto. Esto provoca que la ejecución de un programa sea diferente en cada da pasada y que sea independiente.

Para mostrarlo prácticamente, el siguiente código ilustra este efecto de forma práctica, el cual jugando con el ejemplo anterior y con diferentes períodos de parada sleep consigue que los mensajes sean diferentes a los de partida:

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
// 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>

void* thread_run(void* data)
{ //sleep(2); 
  printf("[TH_1 %ld]: Hello from the thread \n",pthread_self());
  sleep(1);
  (*(int*)data)++;
  printf("[TH_1 %ld]: To exit...............\n",pthread_self());
  pthread_exit(data);
}

int main()
{
  pthread_t thread;
  int data=0;
  int thread_rc=0;
  printf("[MAIN %ld]: Starting............ \n",pthread_self());
  if ((thread_rc=pthread_create(&thread,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;
  pthread_join(thread,(void **)&ptr_output_data);
  printf("[MAIN %ld]: Thread returns %d \n",pthread_self(),*ptr_output_data);
  return 0;
} 

La ejecución de cada hilo es independiente, o mas bien, se ejecuta concurrentemente con el resto y lo que genera la siguiente traza:

 ./pthreads_create_join_main 
[MAIN -1219377472]: Starting............ 
[TH_1 -1219380416]: Hello from the thread 
[MAIN -1219377472]: Thread allocated 
[TH_1 -1219380416]: To exit...............
[MAIN -1219377472]: Thread returns 1 

En esta ejecución se ha jugado con los tiempos del sleep de la aplicación para que los mensajes del hilo principal y del hilo secundario se intercalen. Esto se consigue metiendo un sleep que genera una secuencia predecible, donde en principio esto no es tan sencillo de conseguir.

Alguna de las propiedades que se pueden controlar incluye la particularización de los siguientes parámetros:

  • La configuración de si hilo padre esperará o no por un resultado de su hilo cuando haga pthread_join. Esto se controla mediante pthread_attr_setdetachstate.

  • El tamaño de la pila donde se ejecuta el hilo. Esto es controlable mediante pthread_attr_setstacksize.