POSIX Threads, Hilos o Hebras

Hemos visto procesos y algunas formas para compartir información entre procesos. Ahora veremos cómo usar múltiples hebras o hilos de control para efectuar múltiples tareas dentro de un mismo proceso. Un aspecto ineludible al hablar de compartir recursos son los mecanismos de sincronización necesarios para conservar la consistencia de los recursos compartidos.

Objetivos: Aprender conceptos básicos sobre Hilos, experimentar con los llamados a hilos de POSIX, conocer los mecanismos de sincronización de POSIX.

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


Conceptos de Hilo o hebra
Un proceso típico de Unix puede ser visto como un único hilo de control: los procesos estudiados hasta ahora hacen sólo una cosa a la vez. Con múltiples hilos de control podemos hacer más de una cosa a la vez cuando cada hilo se hace cargo de una tarea.
Beneficios de hilos:
Multi-hilos son útiles en arquitecturas de hardware con uno o múltiples procesadores.
Un hilo contiene la información necesaria para representar un contexto de aplicación dentro de un proceso. Ésta es:
Unix Process Process-thread relationship
Proceso en UNIX Hilos en un proceso Unix

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.
Los hilos de un proceso comparten variables globales, descriptores de archivos abiertos, y pueden cooperar o interferir con otros hilos.
Todas las funciones de hilos del POSIX  comienzan con pthread. Entre ellas están:

Función POSIX
Descripción (páginas man, otra, en Ubuntu podemos cargar el paquete manpages-posix-dev para acceder a estas páginas)
pthread_create crea un hilo (análogo a fork)
pthread_equal verifica igualdad de dos identificados de hilos
pthread_self retorna ID de propio hilo (análogo a getpid)
pthread_exit termina el hilo sin terminar el proceso (análogo a exit)
pthread_join espera por el término de un hilo (análogo a waitpid)
pthread_cancel
Termina otro hilo  (análogo a abort)


pthread_detach
Configura liberación de recursos cuando termina
pthread_kill
envía una señal a un hilo

Para hacer uso de estas funciones incluir <pthread.h> en el código y -pthread en el ligado, como en:
$ cc -o ejecutable -pthread fuente.c


Creación de Hilos
Los procesos normalmente corren como un hilo único. La creación de un nuevo hilo debe ser explícita, se logra vía llamado a pthread_create.
# include <pthread.h>
int pthread_create(pthread_t * restrict tidp,
const pthread_attr_t * restrict attr,
void * ( * start_routine ) (void *),
void * restrict arg);

tidp: salida, puntero a id del hilo
attr: entrada, para definir atributos del hilo, null para default
start_routine: entrada, función a correr por el hilo
arg: entrada, argumento de la función del hilo.
La función debe retornar un * void, el cual es interpretado
como el estatus de término por pthread_join
Nota: restrict es una palabra reservada para señalar al compilador que el dato referenciado sólo será accedido en la función a través de ese puntero. Esto ayuda al compilador a optimizar el código.
Ejemplo: Impresión de thread ID ("apue.h"). Puede bajar todos los fuentes del texto guía, una mirada al Makefile es educativa, se aprende a crear una biblioteca, y cómo generar una compilación parametrizada (también conocida como compilación condicional) según la plataforma en que usted trabaje.
De este ejemplo, notar: El hilo principal debe dormir para asegurar que el nuevo hilo logre hacer su parte.
Ejemplo 2: dos hebras modifican un mismo entero código. Revisar explicación para la lectura del estatus de retorno de la hebra.

Identificación de Hilos
Así como un proceso tiene un PID (Process Identification), cada hilo tiene un identificador de hilo. Mientras los PID son enteros no negativos, el ID de un hilo es  dependiente del SO y puede ser una estructura. Por esto para su comparación se usa una función.
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
Retorna: no cero si es igual, cero en otro caso
Compara dos identificadores de hilos tid1 y tid2 
#include <pthread.h>
pthread_t pthread_self(void);
Retorna: la ID del hilo que la llamó
Para obtener identificador de un hilo


Término de un Hilo
Si un hilo invoca a exit, _Exit o _exit, todo el proceso terminará.
Un hilo puede terminar de tres maneras sin terminar el proceso: Retornando de su rutina de inicio, cancelado por otro hilo del mismo proceso, o llamando pthread_exit.
#include <pthread.h>
void pthread_exit (void * rval_ptr);
rval_ptr queda disponible para otros hilos al llamar pthread_join
rval_ptr debe existir después del término del hilo.
int pthread_join(pthread_t tid, void ** rval_ptr);

Explicación del uso de void ** rval_ptr.
El hilo llamante se bloquea hasta el término del hilo indicado.
Si el hilo en cuestión es cancelado, rval_prt toma el valor
PTHREAD_CANCELED
Si no estamos interesados en el valor retornado, poner NULL.
int pthread_cancel(pthread_t tid);
Permite a un hilo cancelar otro hilo del mismo proceso.
Retorno 0 es OK, !=0 => error.
Equivale a si el hilo, que desamos cancelar, llamara
pthread_exit(PTHREAD_CANCELED); sin embargo, un hilo puede
ignorar este requerimiento o controlar cómo se cancela.
pthread_cancel sólo hace un requerimiento, pero no lo fuerza.
int pthread_setcacelstate(int state, int * oldstate); Permite cambiar el estado del hilo a
PTHREAD_CANCEL_ENABLE (default) o
PTHREAD_CANCEL_DISABLE, en este estado el hilo ignora
llamados a pthread_cancel que le afecten.
Ejemplos: Recuperando el estatus de término de hilos: exitstatus.c
No debemos usar variables automáticas (aquellas en stack) como argumento de pthread_exit, ver badexit2.c porque el acceso a éstas se pierde luego del término de la hebra.

Detaching y Joining en hilos (desasociar y reunión)
Todo hilo ocupa recursos del SO para su operación. Entre ellos se encuentra el estatus de término el cual es retenido hasta el llamado a pthread_join; sin embargo, los recursos ocupados por un hilo pueden ser retornados inmediatamente después que éste termina si llamamos a pthread_detach. En este caso un llamado a pthread_join fallará y retornará EINVAL.
#include <pthread.h>
int pthread_detach(pthread_t tid);
retorna 0 es OK, !=0 => error.
al término del hilo con tid, sus recursos serán retornados
y llamados a pthread_join arrojan error.

Ejemplos:

0.- fork v/s threads: Comparación entre creación de procesos y creación de hilos
1.- Dos hebras: una incrementa y otra decrementa un mismo valor global, sin control de acceso exclusivo. Revisar explicación a lectura del valor retornado en:  pthread_join(tid, (void **)&pStatus);
2.- Ejemplo de valor retornado por hebra. Suma de enteros usando varias hebras.
3.- callcopymalloc.c programa que crea un hilo y copia un archivo. Funciones relacionadas copyfilemalloc.c,  copyfile y otras en restart.c
4.- callcopypass.c ídem anterior, el parámetro pasado es un arreglo con espacio para el valor retornado. Función relacionada copyfilepass.c
5.- copymultiple.c programa que llama copyfilepass en varios hilos para generar múltiples copias de un archivo.