1. Qué son los modelos de hilos
Los modelos de hilos describen como se relacionan los hilos que ve el programador (hilos de usuario) con los hilos que el núcleo gestiona (hilos de núcleo). La pregunta fundamental es: ¿Quién programa los hilos? ¿La biblioteca de usuario o el núcleo?
La respuesta determina el rendimiento, el paralelismo y la complejidad del sistema. Hay tres modelos principales: 1:1, N:1 y M:N. Cada uno tiene ventajas y desventajas.
En el tema anterior viste que linux usa el modelo 1:1. En este tema veremos por que, y que alternativas existen.
2. Hilos de usuario
Los hilos de usuario (user-level threads) son gestionados por una biblioteca en espacio de usuario. El núcleo no sabe que existen: para el núcleo, el proceso tiene un sólo hilo (el que el núcleo programa).
La biblioteca de usuario se encarga de:
- Crear y destruir hilos: asigna una pila y un TCB en espacio de usuario.
- Programar los hilos: decide que hilo se ejecuta y cuándo. Usa un planificador en espacio de usuario.
- Cambiar de contexto: guarda y restaura los registros de la CPU Sin intervencion del núcleo.
- Sincronizar: mutex, semaforos, etc. se implementan en espacio de usuario.
Ventajas:
- Rapidos: crear un hilo y cambiar de contexto es muy rápido (no hay syscall).
- flexible: la biblioteca puede implementar cualquier politica de planificacion.
- Portable: Funcióna en cualquier sistema operativo, incluso sin soporte de hilos en el núcleo.
Desventajas:
- Sin paralelismo real: Como el núcleo sólo ve un hilo, los hilos de usuario no pueden ejecutarse en paralelo en multiples núcleos.
- Un bloqueo bloquea a todos: Sí un hilo hace una syscall bloqueante (read, sleep), todos los hilos del proceso se bloquean (porque el núcleo bloquea al único hilo que conoce).
- complejidad: la biblioteca debe implementar su propio planificador, Sincronizacion, etc.
Los hilos de usuario son rápidos de crear y cambiar, pero no pueden ejecutarse en paralelo en multiples núcleos. Y Sí un hilo se bloquea en una syscall, todos los hilos se bloquean. Esto los hace inadecuados para la mayoria de las aplicaciones modernas.
3. Hilos de núcleo
Los hilos de núcleo (kernel-level threads) son gestionados directamente por el núcleo. El núcleo programa cada hilo independientemente, Como sí fuera un proceso ligero.
Ventajas:
- Paralelismo real: los hilos pueden ejecutarse en paralelo en multiples núcleos.
- No bloquean a todos: Sí un hilo se bloquea en una syscall, los demás hilos del proceso pueden seguir ejecutandose (el núcleo programa otro hilo).
- El núcleo gestiona todo: no se necesita un planificador en espacio de usuario.
Desventajas:
- Mas lentos: crear un hilo y cambiar de contexto requiere una syscall (Más lento que en espacio de usuario).
- Dependen del núcleo: requieren soporte del núcleo para hilos.
Linux, Windows y macOS usan hilos de núcleo. Es el modelo dominante en los sistemás modernos.
4. El modelo 1:1
En el modelo 1:1, cada hilo de usuario corresponde a un hilo de núcleo. Es el modelo más simple y el más usado.
Caracteristicas:
- Paralelismo real: los hilos pueden ejecutarse en paralelo en multiples núcleos.
- No bloquean a todos: Sí un hilo se bloquea, los demás siguen.
- El núcleo gestiona todo: no se necesita planificador en espacio de usuario.
- costo: crear un hilo requiere una syscall (clone), y cambiar de contexto requiere cambio de contexto del núcleo.
Usado por: Linux (pthreads/NPTL), Windows, macOS, Solaris.
5. El modelo N:1
En el modelo N:1, multiples hilos de usuario se mapean a un sólo hilo de núcleo. La biblioteca de usuario programa los hilos entre sí, y el núcleo sólo ve un hilo.
Caracteristicas:
- Rapidos: crear hilos y cambiar de contexto es muy rápido (no hay syscall).
- Sin paralelismo: los hilos no pueden ejecutarse en paralelo (el núcleo sólo ve uno).
- Bloqueo total: Sí un hilo se bloquea en una syscall, todos los hilos se bloquean.
- Planificador en espacio de usuario: la biblioteca implementa su propio planificador.
Usado por: Solaris (green threads, antiguo), Java (green threads, antiguo), Sistemás embebidos sin soporte de hilos en el núcleo.
El modelo N:1 es raro hoy en dia porque la falta de paralelismo y el bloqueo total son limitaciones graves.
6. El modelo M:N
En el modelo M:N, M hilos de usuario se mapean a N hilos de núcleo (con M >= N). La biblioteca de usuario programa los hilos de usuario sobre los hilos de núcleo, Que el núcleo programa sobre la CPU.
Caracteristicas:
- Paralelismo: los hilos pueden ejecutarse en paralelo (Sí N > 1).
- Flexibilidad: la biblioteca puede ajustar N Según la carga.
- complejidad: la biblioteca debe implementar un planificador de dos niveles (usuario y núcleo).
- costo: Más complejo de implementar y mantener.
Usado por: Solaris (antiguo, con libthread), algunos sistemás de tiempo real.
El modelo M:N es el más flexible pero el más complejo. En la práctica, el overhead de la complejidad supera los beneficios, y la mayoria de los sistemás modernos usan 1:1.
7. Comparacion de modelos
| Caracteristica | 1:1 | N:1 | M:N |
|---|---|---|---|
| Paralelismo real | Si | No | Si (Sí N > 1) |
| Bloqueo en syscall | Solo el hilo bloqueado | Todos los hilos | Solo los hilos mapeados al hilo de núcleo bloqueado |
| Creacion de hilo | Lenta (syscall) | Rapida (espacio de usuario) | Rapida (espacio de usuario) |
| Cambio de contexto | Lento (Núcleo) | Rapido (usuario) | Mixto |
| complejidad | Baja | Media | Alta |
| Usado por | Linux, Windows, macOS | Solaris (antiguo), Java (antiguo) | Solaris (antiguo) |
El modelo 1:1 es el dominante porque ofrece paralelismo real y simplicidad, a costa de un poco más de overhead en la creacion. Los modelos N:1 y M:N son raros hoy en dia.
8. Cómo implementa linux los hilos
Linux implementa los hilos con la syscall clone(), Que es una version generalizada de fork. clone permite especificar que recursos se comparten entre el proceso padre y el hijo.
Cuando se crea un hilo pthread, la biblioteca llama a clone con los flags CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND, CLONE_THREAD, CLONE_PARENT, etc. Esto hace que el hilo comparta la memoria, el sistema de archivos, los descriptores, los manejadores de senales, y el grupo de hilos con el proceso padre.
El resultado es que cada hilo pthread es un task_struct separado, con su propio TID, su propia pila, y sus propios registros. Pero comparte la memoria, los archivos, etc. con los demás hilos del proceso.
En linux, no hay una diferencia fundamental entre un proceso y un hilo. Ambos son task_struct. La diferencia esta en los flags de clone:
- Proceso (fork): clone sin CLONE_VM (Memoria separada). El hijo tiene su propia tabla de páginas.
- Hilo (pthread_create): clone con CLONE_VM (Memoria compartida). El hijo comparte la tabla de páginas del padre.
Esto es una de las decisiones de diseno más elegantes de linux: un sólo mecanismo (clone) para crear tanto procesos como hilos, y la diferencia esta en que se comparte.
En linux, no hay una diferencia fundamental entre procesos e hilos. Ambos son task_struct. La diferencia esta en los flags de clone. Esto simplifica el núcleo y hace que los hilos sean tan eficientes como los procesos.
9. NPTL: la biblioteca de hilos de linux
NPTL (Native POSIX Thread library) es la implementación de pthreads en linux. Es la biblioteca que se usa cuándo se compila con -pthread. NPTL se basa en clone() y usa el modelo 1:1.
NPTL proporciona:
- pthread_create: crea un hilo con clone().
- pthread_join: espera a que un hilo termine.
- Mutex, condiciones, semaforos: Sincronizacion en espacio de usuario (con futex para el núcleo).
- TLS (Thread-Local storage): datos especÃficos de cada hilo.
- Senales por hilo: cada hilo tiene su propia máscara de senales.
NPTL es la implementación estandar de pthreads en linux moderno. Reemplazo a linuxthreads en 2003, Cuándo se integro en el núcleo 2.6.
NPTL usa futex (fast userspace mutex) para la sincronizacion: los mutex se implementan en espacio de usuario, y sólo se hace una syscall (futex) Cuándo hay contención (el mutex esta bloqueado por otro hilo). Esto hace que los mutex sean muy rápidos cuándo no hay contención.
10. historia de los hilos en linux
La historia de los hilos en linux tiene varias etapas:
- Linuxthreads (1996): la primera implementación de pthreads en linux. Usaba clone() pero con un modelo que no era completamente POSIX-compliant. Cada hilo tenia su propio PID (no compartia el TGID), lo que causaba problemás con senales y con herramientas como ps.
- NPTL (2003): la implementación moderna. Usa el modelo 1:1 con CLONE_THREAD, por lo que todos los hilos comparten el mismo TGID (PID). Es completamente POSIX-compliant. Se integro en el núcleo 2.6.
- Estado actual: NPTL es la implementación estandar. Todos los sistemás linux modernos usan NPTL. Linuxthreads esta obsoleto.
La transicion de linuxthreads a NPTL fue importante porque resolvio muchos problemás de compatibilidad con POSIX y mejoro el rendimiento de los hilos.
En cualquier sistema linux moderno, la biblioteca de hilos es NPTL. Se puede verificar con getconf GNU_LIBPTHREAD_VERSION o ldd --Version. Si dice "NPTL", estas usando la implementación moderna.
11. Hilos en otros sistemas
Los hilos se implementan de forma distinta en otros sistemas:
| Sistema | Implementacion | Modelo | Caracteristicas |
|---|---|---|---|
| Linux | NPTL (pthread) | 1:1 | Basado en clone(). Completamente POSIX-compliant. |
| Windows | Createthread (Win32) | 1:1 | Hilos son ciudadanos de primera clase. TEB (Thread environment block). |
| macOS | pthread (basado en mach) | 1:1 | Basado en el mach kernel. Cada hilo es un mach thread. |
| FreeBSD | libthr (pthread) | 1:1 | Basado en el modelo 1:1 desde freeBSD 7. |
| Solaris | libthread | M:N (historico), 1:1 (moderno) | Fue el pionero del modelo M:N. Ahora usa 1:1. |
La tendencia general es hacia el modelo 1:1, porque ofrece paralelismo real y simplicidad. Los modelos N:1 y M:N se consideran obsoletos.
12. Un programa que demuestra modelos
El siguiente programa muestra como se pueden crear hilos con diferentes atributos y observar su comportamiento:
#define _POSIX_C_SOURCE 200809L
#include
#include
#include
#include
#include
void *trabajo(void *arg)
{
int id = *(int *) arg;
printf("Hilo %d: PID=%d TID=%ld (gettid)\n",
id, (int) getpid(), (long) syscall(SYS_gettid));
sleep(1);
return NULL;
}
int main(void)
{
pthread_t hilos[4];
int ids[4] = {1, 2, 3, 4};
printf("Proceso principal: PID=%d TID=%ld\n",
(int) getpid(), (long) syscall(SYS_gettid));
for (int i = 0; i < 4; i++)
pthread_create(&hilos[i], NULL, trabajo, &ids[i]);
for (int i = 0; i < 4; i++)
pthread_join(hilos[i], NULL);
printf("Todos los hilos terminaron\n");
return 0;
}
$ gcc -std=c17 -Wall -Wextra -pthread -o modelos_hilos modelos_hilos.c
$ ./modelos_hilos
Proceso principal: PID=1100 TID=1100
Hilo 1: PID=1100 TID=1101
Hilo 2: PID=1100 TID=1102
Hilo 3: PID=1100 TID=1103
Hilo 4: PID=1100 TID=1104
Todos los hilos terminaron
El programa demuestra el modelo 1:1: todos los hilos tienen el mismo PID (1100) pero TIDs distintos (1101-1104). Cada hilo es un task_struct separado en el núcleo, pero comparten el mismo TGID (PID).
13. Resumen
- Los hilos de usuario son gestionados por una biblioteca en espacio de usuario. Rapidos pero sin paralelismo real.
- Los hilos de núcleo son gestionados por el núcleo. Paralelismo real pero más lentos.
- El modelo 1:1 mapea cada hilo de usuario a un hilo de núcleo. Es el modelo de linux, Windows y macOS.
- El modelo N:1 mapea multiples hilos de usuario a un hilo de núcleo. Sin paralelismo real.
- El modelo M:N mapea M hilos de usuario a N hilos de núcleo. flexible pero complejo.
- Linux usa clone() para crear hilos, con CLONE_VM para compartir memoria.
- NPTL es la implementación de pthreads en linux moderno.
- Linux no distingue procesos de hilos: ambos son task_struct con diferentes flags de clone.
14. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. Qué son los hilos de usuario?
Hilos gestionados por una biblioteca en espacio de usuario. El núcleo no sabe que existen. Rapidos de crear pero sin paralelismo real.
2. Qué son los hilos de núcleo?
Hilos gestionados directamente por el núcleo. El núcleo programa cada hilo independientemente. Paralelismo real pero más lentos.
3. Qué es el modelo 1:1?
Cada hilo de usuario corresponde a un hilo de núcleo. Es el modelo de linux, Windows y macOS.
4. Qué es el modelo N:1?
Multiples hilos de usuario se mapean a un sólo hilo de núcleo. Sin paralelismo real. Usado por sistemás antiguos.
5. Qué es el modelo M:N?
M hilos de usuario se mapean a N hilos de núcleo. flexible pero complejo. Usado por solaris (antiguo).
6. Cómo implementa linux los hilos?
Con clone() y los flags CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND, CLONE_THREAD, etc. Cada hilo es un task_struct separado.
7. Qué es NPTL?
Native POSIX Thread library: la implementación de pthreads en linux moderno. Usa el modelo 1:1 con clone().
8. Qué es linuxthreads?
La primera implementación de pthreads en linux (1996). Cada hilo tenia su propio PID. Fue reemplazada por NPTL en 2003.
9. Por que linux no distingue procesos de hilos?
Porque ambos son task_struct. La diferencia esta en los flags de clone(): CLONE_VM compartido = hilo, no compartido = proceso.
10. Qué es futex?
Fast userspace mutex: los mutex se implementan en espacio de usuario, y sólo se hace una syscall (futex) Cuándo hay contención. Hace que los mutex sean muy rápidos.
Para prácticar
- Compila y ejecuta modelos_hilos.c. Observa que todos los hilos tienen el mismo PID pero TIDs distintos.
- Verifica la version de NPTL con getconf GNU_LIBPTHREAD_VERSION.
- Lee /proc/PID/task para ver los hilos de un proceso.
- Usa ps -L -p PID para ver los hilos de un proceso.
- Escribe un programa que cree un hilo y verifique que comparte la memoria con el padre.
- Investiga la diferencia entre linuxthreads y NPTL.