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: cada proceso hace 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:
- Se puede manejar eventos asíncronos asignando un hilo a
cada tipo de evento. Luego cada hilo maneja sus eventos en forma sincrónica.
- Los hilos de un proceso comparten el mismo espacio de direcciones
y descriptores de archivos.
- Procesos con múltiples tareas independientes pueden
terminar antes si estas tareas se desarrollan traslapadamente en
hilos separados. De este modo los tiempos de espera de la primera tarea no retrasan la segunda.
- Programas interactivos pueden lograr mejor tiempo de respuesta
usando hilos para manejar la entrada y salida. Este es un ejemplo del
punto previo.
- La creación de un hilo es mucho más rápida y
toma menos recursos que la creación de un proceso (fork).
Multi hilos se aplica a máquinas 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:
- ID del hilo. No son únicos dentro del sistema, sólo tienen sentido en el contexto de cada proceso.
- Stack pointer
- Un conjunto de registros
- Propiedades de itineración (como política y
prioridad)
- Conjunto de señales pendientes y bloquedas.
- Datos específicos del hilo.
|
|
Proceso en UNIX (tomado de aquí) |
Hilos en un proceso Unix (tomado de aquí)
|
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 puede 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_equal |
verifica igualdad de dos
identificados de hilos |
pthread_self |
retorna ID de propio hilo
(análogo a getpid)
|
pthread_create
|
crea un hilo (análogo a fork)
|
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>, el ligado
debe incluir -l thread , como en:
$ cc -o
ejecutable -l thread fuente.c
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 identificados 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
|
Creación de Hilos
Los procesos normalmente corren como un
hilo único. La creación de un nuevo hilo se logra
ví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 permite al compilador hacer optimización.
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 por la
plataforma en que usted trabaje.
Notar:
El hilo principal debe dormir para asegurar que el hilo logre hacer su
parte. El hilo nuevo debe llamar a pthread_self() porque no hay
garantía que el valor pasado por el hilo principal lo tenga. Todo esto
es por desconocer qué hilo corre primero.
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);
|
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 indicado 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 en pthread_exit, ver badexit2.c |
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.- callcopymalloc.c
programa que crea un hilo y copia un archivo. Funciones relacionadas copyfilemalloc.c,
copyfile y otras en restart.c
2.- callcopypass.c
ídem
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.