POSIX Threads, Hilos o Hebras


Objetivos: Aprender conceptos básicos sobre Hilos, Experimentar los llamados a hilos de POSIX.

POSIX significa Portable Operating System Interface (for Unix).  Es un estándar orientado a facilitar la creación de aplicaciones aplicaciones confiables y portables. La mayoría de las versiones populares de UNIX ( Linux, Mac OS X) están cumpliendo este estándar en gran medida.

Vimos que un método para lograr paralelismo es crear múltiple procesos y cooperar sincronizadamente usando mecanismos de compartición de memoria. Una forma alternativa de lograrlo es la creación de múltiples hilos de ejecución en un mismo espacio de memoria.

Motivación: Monitoreo de descriptores de archivo

Como vimos con el uso de select, si requerimos monitorear varios descriptores de archivo tenemos varias opciones que hemos visto a la fecha:
  1. Crear un proceso separado para monitorear cada descriptor.
  2. Hacer uso de select
  3. Hacer uso de poll
Ahora veremos un nuevo método, crear hilos separados para monitorear cada hilo.
La primera opción requiere usar mecanismos de compartición de memoria cuando hay información a compartir entre los procesos.
Las dos opciones siguientes generalmente requieren un chequeo regular de los descriptores (poll) o esperar bloqueados por actividad (select). Si se requiere hacer alguna tarea además de monitorear los descriptores, el diseño del programa se dificulta por permanecer bloqueado u obligados a revisar recurrentemente los descriptores.

La opción con hilos es generalmente más simple, reduce el problema a procesar un único descriptor, y se puede traslapar con otro procesamiento en forma transparente.

Comparación proceso/hilo

Un proceso es una instancia de un programa que se está ejecutando. Cada instancia tiene su propio espacio de memoria y estado de ejecución. El SO sigue la pista de los PIDs y los estados correspondientes y usa esta información para asignar y administrar los recursos del sistema. Un proceso tiene un espacio de memoria y tiene al menos un flujo de control llamado hilo.
Cuando un programa corre, el valor del contador de programa determina qué instrucción será la próxima en ejecutar. La secuencia de instrucciones resultante es llamada hilo de ejecución.
Una extensión natural del modelo de procesos es permitir múltiples hilos correr dentro del mismo proceso. Múltiples hilos evitan hacer cambios de contexto y permiten compartir código y datos. Un hilo es un tipo de dato abstracto que representa un hilo de ejecución dentro de un proceso. Un hilo tiene su propio stack de ejecución, contador de programa, conjunto de registros y estado. Como entre varios hilos de un mismo proceso se comparte el mismo espacio de memoria del proceso se requiere mecanismos de sincronización.
En ocasiones los hilos son llamados procesos livianos.

Administración de Hilos

Un paquete de manejo de hilos generalmente incluye funciones para: crear y destruir un hilo, itineración, forzar exclusión mutua y espera condicionada.
Cuando un hilo es creado se crea una estructura que aloja un ID para el hilo, stack, contador de programa, estado del hilo.  Los hilos de un proceso comparten variables globales, descriptores de archivos abiertos, y puede cooperar o interferir con otros hilos.
Todas las funciones de hilos del POSIX  comienzan con pthread. Ellas son:

Función POSIX
Descripción (páginas man, otra)
pthread_create
crea un hilo
pthread_cancel
Termina otro hilo
pthread_detach
Configura liberación de recursos cuando termina
pthread_equal
verifica igualdad de dos identificados de hilos
pthread_exit
termina el hilo sin terminar el proceso
pthread_kill
envía una señal a un hilo
pthread_join
espera por el término de un hilo (similar a waitpid)
pthread_self
retorna ID de propio hilo

Para hacer uso de estas funciones incluir <pthread.h>

Creación de un hilo

Sinopsis:
# include <pthread.h>
int pthread_create(pthread_t * threadID, const pthread_attr_t * attr, void * ( * start_routine ) (void *), void * arg);

thread ID es el identificador para ese hilo.
attr se usa para indicar atributos para el hilo. Si usamos null el nuevo hilo tomará los atributos por omisión.
start_routine es el nombre de la función que se llamará cuando el hilo comience su ejecución. Esta función tiene un sólo parámetro especificado por arg. La función llamada debe retornar un puntero a void, el cual es interpretado como el estatus de término por pthread_join.

Detaching y Joining (desasociar y reunión)

#include <pthread.h>
int pthread_detach(pthread_t thread);

Cuando un hilo termina (pthread_exit), no librea sus recursos a menos que es desasociada (detach).
Esta función especifica que el almacenamiento de un hilo puede ser reclamado por el sistema cuando ésta termina. Un hilo desasociado no reporta su su estado cuando termina. Un hilo no desasociado puede ser reunidos (esperamos por su término) y no retornan todos sus recursos hasta que que otro hilo llame a pthread_join o el proceso completo termine (exit).

int pthread_join (pthread_t thread, void ** value_ptr);

Esta función suspende el hilo llamante hasta que el hilo del argumento termine. value_ptr provee la localización del puntero al estatus de retorno enviado por el hilo con pthread_exit o return.

Término y cancelación

Un proceso puede terminar llamando a exit o invocando a return desde el main o cuando algún hilo del proceso llamar a exit.

void pthread_exit (void * value_ptr);

value_ptr debe apuntar a un dato que exista después del término del hilo.

Un hilo puede forzar a otro a que retorne a través de su cancelación.

int pthread_cancel(pthread_t thread);
este llamado pide la cancelación de otro hilo. este llamado no es bloqueante.
Si un hilo recibe una cancelación y se encuentra en estado PTHREAD_CANCEL_DISABLE, la cancelación queda pendiente. SI su estado es PTHREAD_CANCEL_ENABLE el hilo será cancelado.

Un hilo puede habilitar o no la cancelación llamando a:
int pthread_setcacelstate(int state, int * oldstate);

Ejemplos:
1.- callcopymalloc.c    programa que crea un hilo y copia un archivo. Funciones relacionadas copyfilemalloc.c,  copyfile y otras en restart.c
2.- callcopypass.c  idem anterior. el parámetro pasado es un arreglo con espacio para el valor retornado. Función relacionada copyfilepass.c
3.- copymultiple.c  programa que llama copyfilepass en varios hilos para generar múltiples copias de un archivo.