
asyncio: Cómo Funcionan los Procesos, Hilos y el Event Loop
Tabla de Contenido
En el artículo anterior
construimos un modelo mental sobre concurrencia, paralelismo y los tipos de multitarea. Ahora vamos a bajar un nivel: entender qué son los procesos y los hilos, por qué el GIL de Python cambia las reglas del juego, y cómo asyncio logra concurrencia con un solo hilo gracias al event loop.
Procesos e Hilos
¿Qué es un proceso?
Un proceso es una instancia de una aplicación en ejecución. Cada proceso tiene su propio espacio de memoria aislado — ningún otro proceso puede acceder a él directamente — y se ejecuta en un CPU o núcleo del sistema.
¿Qué es un hilo (thread)?
Un hilo es una unidad de ejecución más ligera que vive dentro de un proceso. A diferencia de los procesos, los hilos no tienen su propia memoria: comparten el espacio de memoria del proceso que los creó.
Todo proceso tiene al menos un hilo asociado, conocido como el hilo principal (main thread). Además, un proceso puede crear hilos adicionales, comúnmente llamados hilos de trabajo (worker threads) o hilos en segundo plano (background threads).
Info
Todos los hilos dentro de un proceso comparten la misma memoria. Esto hace la comunicación entre hilos muy rápida, pero introduce el riesgo de condiciones de carrera si no se maneja con cuidado.
Verificándolo en Python
Puedes inspeccionar el proceso e hilo actual con los módulos os y threading:
import os
import threading
print(f'Proceso de Python con PID: {os.getpid()}')
total_threads = threading.active_count()
thread_name = threading.current_thread().name
print(f'Python está ejecutando {total_threads} hilo(s)')
print(f'El hilo actual es: {thread_name}')
# Output esperado
Proceso de Python con PID: 48672
Python está ejecutando 1 hilo(s)
El hilo actual es: MainThread
Por defecto, tu programa Python corre en un solo hilo (MainThread) dentro de un solo proceso.
El Global Interpreter Lock (GIL)
El GIL (Global Interpreter Lock) es uno de los temas más debatidos en la comunidad Python. En términos simples: el GIL impide que un proceso de Python ejecute más de una instrucción de bytecode a la vez, incluso si tienes múltiples hilos.
Esto significa que aunque crees varios hilos con threading, solo uno ejecuta código Python en cualquier instante.
¿El GIL se libera alguna vez?
Sí. El GIL se libera durante las operaciones de I/O. Cuando un hilo hace una petición de red o una lectura de disco, libera el GIL, permitiendo que otro hilo tome el control y ejecute código Python mientras tanto.
Esto es lo que hace que threading sea útil para trabajo I/O-bound, pero ineficaz para trabajo CPU-bound.
asyncio y el GIL
asyncio aprovecha exactamente este comportamiento: como las operaciones de I/O liberan el GIL, puede lograr concurrencia con un solo hilo. En lugar de crear múltiples hilos, asyncio crea objetos llamados coroutines — que puedes pensar como hilos ultra-ligeros.
Note
Mientras una coroutine espera por una operación de I/O, el event loop ejecuta otras coroutines que estén listas. El resultado: concurrencia sin los problemas de sincronización del multithreading.
Concurrencia de Un Solo Hilo
La concurrencia de un solo hilo no se basa en hacer dos cosas simultáneamente (eso sería paralelismo), sino en aprovechar los tiempos de espera de las operaciones de I/O para ejecutar otras tareas. El mecanismo se apoya en tres pilares:
- Sockets no bloqueantes
- Delegación al SO
- Event Loop
1. Sockets no bloqueantes
A diferencia de un socket estándar que detiene el programa hasta recibir una respuesta, un socket no bloqueante permite enviar una petición y devolver el control al programa inmediatamente, sin esperar el resultado.
2. Delegación al sistema operativo
Cuando el código llega a una operación de red o lectura de disco, Python le delega la espera al sistema operativo. El hilo de ejecución queda libre de inmediato.
3. El Event Loop como coordinador
Mientras el SO vigila los sockets, el hilo de Python sigue ejecutando otras tareas. Cuando el SO detecta que los datos llegaron, envía una notificación. El event loop recibe este aviso y “despierta” la tarea pausada para que procese la respuesta.
El siguiente diagrama de secuencia muestra cómo interactúan estos tres pilares:
Cómo Funciona el Event Loop
En su forma más básica, un event loop es simplemente un bucle infinito que procesa mensajes de una cola, uno a la vez:
from collections import deque
messages = deque()
while True:
if messages:
message = messages.pop()
process_message(message)
En asyncio, el event loop mantiene una cola de tasks (tareas) en lugar de mensajes simples. Cada task es un wrapper alrededor de una coroutine. Cuando una coroutine alcanza una operación de I/O, se pausa y le devuelve el control al event loop, que puede ejecutar otras tareas que no estén esperando I/O.
Note
Este ciclo se repite indefinidamente hasta que todas las tareas se completan o el programa se cierra explícitamente. Si una coroutine ejecuta trabajo CPU-bound sin ceder control con await, bloqueará todo el event loop y ninguna otra tarea podrá avanzar.
En conclusión
- Un proceso tiene memoria aislada; un hilo comparte memoria dentro de su proceso.
- El GIL impide paralelismo real con hilos en Python, pero se libera durante operaciones de I/O.
asyncioaprovecha esta liberación del GIL para lograr concurrencia en un solo hilo mediante coroutines.- El event loop coordina las tareas: ejecuta coroutines hasta que alcanzan un
await, las pausa, y reanuda otras que ya tengan datos listos. - Los sockets no bloqueantes y la delegación al SO son los mecanismos que permiten esta concurrencia sin crear múltiples hilos.


