Volver a Procesos e Hilos Bloque 9 - Procesos frente a hilos en la práctica - Tema 37 de 43

Tema 37

Comparar costos y aislamiento

Procesos e hilos resuelven el mismo problema (concurrencia) de formas distintas. Los procesos ofrecen aislamiento pero son costosos. Los hilos son baratos pero comparten memoria. En este tema mediremos los costos de creación, memoria y cambio de contexto, y analizaremos el aislamiento y la seguridad de cada modelo.

1. Por qué comparar

Los procesos y los hilos son las dos formas de concurrencia en un sistema operativo. Ambos permiten que varias tareas avancen simultáneamente, pero con costos y garantías distintas. La elección entre uno y otro afecta el rendimiento, la seguridad y la complejidad del programa.

En este tema mediremos los costos de creación, memoria y cambio de contexto de ambos modelos, y analizaremos el aislamiento y la seguridad. Esto nos permitirá tomar decisiones informadas en los temas siguientes (servidores, navegadores, videojuegos).

No hay un ganador absoluto

Ni los procesos ni los hilos son siempre mejores. La elección depende del caso de uso: si se necesita aislamiento y seguridad, los procesos son mejores. Si se necesita rendimiento y comunicación fácil, los hilos son mejores. Muchos sistemas usan ambos: procesos para aislamiento, hilos para paralelismo dentro de cada proceso.

2. Costo de creación

Crear un proceso (fork) es más costoso que crear un hilo (pthread_create), porque fork crea una nueva tabla de páginas y configura el PCB. Pero con COW, el costo de fork es mucho menor que en el pasado.

OperaciónTiempo típicoPor qué
fork() (proceso)~10-50 usCrea tabla de páginas, PCB, copia COW. El costo crece con el tamaño de la memoria (por la tabla de páginas).
pthread_create (hilo)~1-5 usCrea task_struct, asigna pila. No copia memoria (comparte el espacio de direcciones).
clone (interno)~1-5 usLa syscall subyacente. El costo depende de los flags (CLONE_VM, etc.).

Los hilos se crean 5-10 veces más rápido que los procesos. Esto es importante en sistemas que crean y destruyen tareas frecuentemente (servidores web, pools de conexiones).

Sin embargo, el costo de creación es solo una parte del costo total. Un proceso que se crea una vez y dura horas tiene un costo de creación amortizado. Un hilo que se crea y destruye miles de veces por segundo tiene un costo de creación significativo.

3. Costo de memoria

La memoria es donde la diferencia entre procesos e hilos es más marcada:

RecursoProcesoHilo
Espacio de direccionesPropio (tabla de páginas separada)Compartido (misma tabla de páginas)
Código, datos, heapCopia (COW, pero la tabla de páginas es nueva)Compartido (sin copia)
PilaPropia (8 MB virtual, ~páginas usadas)Propia (8 MB virtual, ~páginas usadas)
PCB/TCBtask_struct (~10 KB)task_struct (~10 KB, pero comparte mm_struct)
Memoria por unidad~10-50 KB (PCB + tabla de páginas) + memoria del programa~10 KB (TCB) + pila (8 MB virtual)

Los hilos son más eficientes en memoria porque comparten el espacio de direcciones y la tabla de páginas. Pero cada hilo tiene su propia pila (8 MB virtual por omisión), lo que puede ser significativo si hay muchos hilos.

Para reducir el consumo de memoria de los hilos, se puede reducir el tamaño de la pila con pthread_attr_setstacksize(). Por ejemplo, una pila de 64 KB en lugar de 8 MB reduce el consumo virtual en 128x.

La pila es el costo oculto de los hilos

Cada hilo reserva 8 MB de espacio virtual para su pila. Con 1000 hilos, son 8 GB de espacio virtual. Aunque no toda la memoria se usa físicamente (solo las páginas tocadas), el espacio virtual se cuenta para el límite de memoria del proceso (RLIMIT_AS, ulimit -v).

4. Costo de cambio de contexto

El cambio de contexto entre procesos es más costoso que entre hilos, porque los procesos tienen espacios de direcciones distintos:

Tipo de cambioTablas de páginasTLBCosto típico
Entre procesosCambianSe invalida~5-10 us
Entre hilos del mismo procesoNo cambianNo se invalida~1-3 us

La diferencia se debe a la invalidación de la TLB (Translation Lookaside Buffer). Cuando se cambia de proceso, la TLB se invalida, y las primeras accesos a memoria del nuevo proceso causan fallos de TLB (costosos). Cuando se cambia de hilo, la TLB no se invalida, porque las tablas de páginas son las mismas.

En sistemas modernos con PCID (Process Context ID), la TLB no se invalida completamente al cambiar de proceso, lo que reduce el costo. Pero sigue siendo más costoso que cambiar entre hilos.

Los sistemas con muchos cambios de contexto (por ejemplo, un servidor con miles de hilos) pueden beneficiarse de usar hilos en lugar de procesos, porque el cambio de contexto es más barato.

5. Costo de comunicación

La comunicación entre procesos requiere IPC (pipes, sockets, memoria compartida, etc.), que tiene un costo. La comunicación entre hilos es directa (memoria compartida), pero requiere sincronización (mutex).

MecanismoCosto típicoPor qué
Pipe (entre procesos)~1-5 usSyscall read/write, copia de datos entre espacios de direcciones.
Socket (entre procesos)~5-20 usSyscall, copia de datos, protocolo de red.
Memoria compartida (entre procesos)~0.01 us (lectura) + sincronizaciónAcceso directo a memoria, pero requiere mutex o semáforo.
Variable compartida (entre hilos)~0.01 us (lectura) + mutex (~0.02-5 us)Acceso directo a memoria. El mutex añade costo si hay contención.

Los hilos tienen una ventaja clara en comunicación: compartir memoria es más rápido que IPC. Pero la sincronización (mutex) añade costo. Si la comunicación es frecuente y la sincronización es simple (sin contención), los hilos son más eficientes.

Si la comunicación es infrecuente o requiere sincronización compleja, la diferencia se reduce. Y si se necesita aislamiento (que un error en una tarea no afecte a las demás), los procesos son mejores.

6. Aislamiento

El aislamiento es la capacidad de un componente de fallar sin afectar a los demás. Los procesos ofrecen aislamiento completo: si un proceso falla (segfault, etc.), el núcleo lo termina y los demás procesos no se ven afectados. Los hilos no ofrecen aislamiento: un error en un hilo puede corromper la memoria de todos los hilos del proceso.

AspectoProcesosHilos
MemoriaAislada (espacio de direcciones propio)Compartida (mismo espacio de direcciones)
Fallo de un componenteNo afecta a los demásPuede corromper la memoria de todos
DepuraciónMás fácil (aislado)Más difícil (estado compartido)
SeguridadMayor (un proceso no puede ver la memoria de otro)Menor (todos los hilos ven la misma memoria)

El aislamiento es la razón por la que los navegadores modernos usan procesos para las pestañas (tema 39): si una pestaña falla, no afecta a las demás. Los hilos no ofrecen esta garantía.

Sin embargo, el aislamiento tiene un costo: los procesos son más costosos de crear y comunicar. La elección entre aislamiento y rendimiento es una de las decisiones más importantes en el diseño de sistemas.

7. Seguridad

La seguridad es otro factor clave en la elección:

Si un programa procesa datos de diferentes usuarios o de diferentes niveles de confianza, los procesos son más seguros. Si todos los hilos operan con el mismo nivel de confianza, los hilos son aceptables.

Los hilos no son un límite de seguridad

En un programa con hilos, todos los hilos tienen el mismo UID y los mismos permisos. Si un hilo es comprometido (por ejemplo, por una vulnerabilidad), el atacante tiene acceso a toda la memoria del proceso. Los procesos ofrecen un límite de seguridad que los hilos no tienen.

8. Cuándo usar procesos

Los procesos son la mejor opción cuando:

Ejemplos: servidores web (un proceso por sitio), navegadores (un proceso por pestaña), shells (un proceso por comando).

9. Cuándo usar hilos

Los hilos son la mejor opción cuando:

Ejemplos: servidores de alto rendimiento (un hilo por conexión), videojuegos (hilos de render, física, audio), aplicaciones con UI (hilo de UI + hilo de trabajo).

10. Medir costos en la práctica

Para medir los costos de creación, memoria y cambio de contexto, se usan herramientas como:

MétricaHerramientaCómo medirla
Tiempo de creaciónclock_gettime, /proc/PID/statMedir el tiempo antes y después de fork/pthread_create.
Memoria (VmRSS)/proc/PID/status, /proc/PID/statmLeer VmRSS después de crear N procesos/hilos.
Cambios de contexto/proc/PID/status, vmstatLeer voluntary_ctxt_switches y nonvoluntary_ctxt_switches.
Tiempo de CPU/proc/PID/stat (utime, stime)Medir el tiempo de CPU consumido por cada modelo.
ThroughputBenchmark propioMedir cuántas tareas se completan por segundo.

La forma más precisa de medir es con perf stat, que usa los contadores de hardware del procesador:

terminal - perf stat

$ perf stat -e context-switches,cpu-migrations,page-faults ./programa
...
       1234      context-switches
          5      cpu-migrations
       5678      page-faults

perf stat muestra los contadores de hardware: cambios de contexto, migraciones entre CPUs, y fallos de página. Es la herramienta más precisa para medir el rendimiento.

11. Un programa que mide y compara

El siguiente programa mide el tiempo de creación y el consumo de memoria de procesos e hilos:

medir_costos.c

#define _POSIX_C_SOURCE 200809L
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static double tiempo_ms(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
}

static void medir_procesos(int n)
{
    double t0 = tiempo_ms();
    for (int i = 0; i < n; i++) {
        pid_t pid = fork();
        if (pid == 0) _exit(0);
        if (pid < 0) { perror("fork"); return; }
    }
    for (int i = 0; i < n; i++) wait(NULL);
    double t1 = tiempo_ms();
    printf("Procesos: %d creados en %.2f ms (%.2f us/proceso)\n",
           n, t1 - t0, (t1 - t0) * 1000.0 / n);
}

static void *hilo_vacio(void *arg) { (void) arg; return NULL; }

static void medir_hilos(int n)
{
    pthread_t *hilos = malloc(n * sizeof *hilos);
    double t0 = tiempo_ms();
    for (int i = 0; i < n; i++)
        pthread_create(&hilos[i], NULL, hilo_vacio, NULL);
    for (int i = 0; i < n; i++)
        pthread_join(hilos[i], NULL);
    double t1 = tiempo_ms();
    printf("Hilos:    %d creados en %.2f ms (%.2f us/hilo)\n",
           n, t1 - t0, (t1 - t0) * 1000.0 / n);
    free(hilos);
}

int main(void)
{
    int n = 100;
    printf("Creando %d procesos e hilos...\n\n", n);
    medir_procesos(n);
    medir_hilos(n);
    return 0;
}
terminal - ejecutar medir_costos

$ gcc -std=c17 -Wall -Wextra -pthread -o medir_costos medir_costos.c
$ ./medir_costos
Creando 100 procesos e hilos...

Procesos: 100 creados en 2.50 ms (25.00 us/proceso)
Hilos:    100 creados en 0.50 ms (5.00 us/hilo)

Los hilos se crean 5 veces más rápido que los procesos. Esta diferencia se mantiene incluso con COW, porque fork sigue necesitando crear una tabla de páginas.

12. Resumen

13. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. Cuánto tarda crear un proceso vs un hilo?

fork: ~10-50 us. pthread_create: ~1-5 us. Los hilos son 5-10x más rápidos.

2. Por qué el cambio de contexto entre procesos es más costoso?

Porque los procesos tienen espacios de direcciones distintos, lo que requiere cambiar las tablas de páginas e invalidar la TLB.

3. Cuál es el costo oculto de los hilos?

La pila: cada hilo reserva 8 MB de espacio virtual. Con muchos hilos, el espacio virtual se agota.

4. Cuándo usar procesos?

Cuando se necesita aislamiento, seguridad, robustez, o se ejecutan programas diferentes.

5. Cuándo usar hilos?

Cuando se necesita rendimiento, comunicación fácil, paralelismo, o responsividad.

6. Qué es perf stat?

Una herramienta que usa los contadores de hardware del procesador para medir context-switches, page-faults, cpu-migrations, etc.

7. Los hilos ofrecen aislamiento?

No. Un error en un hilo puede corromper la memoria de todos los hilos del proceso. Los procesos ofrecen aislamiento completo.

8. Los hilos ofrecen seguridad?

No. Todos los hilos comparten el mismo UID/GID y permisos. Los procesos ofrecen límites de seguridad.

9. Cómo reducir el consumo de memoria de los hilos?

Reduciendo el tamaño de la pila con pthread_attr_setstacksize (por ejemplo, 64 KB en lugar de 8 MB).

10. Muchos sistemas usan ambos modelos?

Sí. Procesos para aislamiento (un proceso por sitio/pestaña), hilos para paralelismo dentro de cada proceso.

Para practicar

  1. Compila y ejecuta medir_costos.c. Observa la diferencia entre procesos e hilos.
  2. Usa perf stat -e context-switches,page-faults ./medir_costos para medir contadores de hardware.
  3. Modifica medir_costos.c para medir el consumo de memoria (VmRSS) de cada modelo.
  4. Escribe un benchmark que mida el tiempo de comunicación entre procesos (pipe) vs hilos (variable compartida).
  5. Reduce la pila de los hilos a 64 KB y mide el consumo de memoria.
  6. Compara el rendimiento de un servidor con procesos vs un servidor con hilos.