Elección de un Modelo de Concurrencia
Elige asyncio, hilos (threads) o procesos basándote en la forma de la carga de trabajo, las API de las bibliotecas y la operabilidad, no en la moda. Esta lista de verificación te guía desde los datos de perfilado hasta una elección defendible.
Cómo Usar Esta Lista de Verificación
- Primero, perfila (
py-spy,cProfile, APM): las etiquetas mienten. - Documenta la elección en un ADR (Architecture Decision Record) cuando el servicio tenga una larga vida útil.
- Revisa después de cambios en las dependencias (SDK síncrono a asíncrono).
Clasificación de la Carga de Trabajo
- Mide la espera dominante: Si más del 60% del tiempo se espera en E/S, trátalo como limitado por E/S; si la CPU satura los núcleos, limitado por CPU.
- Forma de la API de la biblioteca: Cliente asíncrono nativo -> asyncio; solo bloqueante -> hilos o
to_thread. - Necesidad de paralelismo: Muchos sockets concurrentes -> asyncio; pocas llamadas bloqueantes -> pequeño pool de hilos.
- Núcleos de CPU: Bucles de CPU en Python ->
ProcessPoolExecutor; las extensiones de NumPy/C pueden liberar el GIL en hilos. - Aislamiento: Código nativo propenso a fallos -> procesos; caché compartida en memoria -> hilos/async con cuidado.
Tabla de Decisión del Modelo
| Perfil | Primera opción | Alternativa |
|---|---|---|
| Muchas esperas HTTP/DB, bibliotecas asíncronas | asyncio | ThreadPool + bibliotecas bloqueantes |
| SDK bloqueante, concurrencia moderada | ThreadPoolExecutor | asyncio + to_thread |
| Python puro de CPU | ProcessPoolExecutor | Extensión C / vectorizar |
| Etapa mixta de API + CPU | asyncio + pool de procesos | División de pool de hilos + pool de procesos |
| Script simple, <5 tareas | Secuencial | hilos si la E/S es lenta |
Flujo de Trabajo Paso a Paso
Paso 1: ¿La tarea está limitada por E/S?
Si es así y existen bibliotecas asíncronas capaces, prefiere asyncio para cientos o más esperas concurrentes. Si las bibliotecas son bloqueantes, usa ThreadPoolExecutor dimensionado a los límites de conexión.
Paso 2: ¿La tarea está limitada por CPU en bytecode de Python?
Si es así en la compilación predeterminada con GIL, usa multiprocessing o ProcessPoolExecutor. No esperes una mejora de velocidad con hilos para bucles ajustados.
Paso 3: ¿La carga de trabajo es mixta?
Divide el pipeline: obtención asíncrona -> asyncio.to_thread o run_in_executor(ProcessPool) para la transformación. Mantén el trabajo de CPU fuera del hilo del bucle de eventos.
Paso 4: ¿Cuáles son las restricciones de operabilidad?
Windows requiere que los trabajadores sean "picklable" (serializables) para el spawn; los hilos demonio complican el apagado ordenado; la memoria de los procesos es la línea base × número de trabajadores. Tenlo en cuenta en tu elección.
Paso 5: ¿Has medido después de implementar?
Compara la latencia p50/p99 y la CPU bajo carga realista. Un modelo incorrecto se manifestará como bloqueo del bucle de eventos, contención del GIL o "thrash" (agitación excesiva) en la creación de procesos.
Preguntas Frecuentes
¿Predeterminado para servicios FastAPI?
asyncio: el framework es nativo asíncrono; descarga el trabajo bloqueante al pool de hilos con moderación.
¿Cuándo es aceptable el modo secuencial?
Scripts de administración de bajo QPS, cron con una sola conexión, prototipos antes de la prueba de escala.
¿Puedo mezclar hilos y asyncio?
Sí, a través del ejecutor: nunca llames a E/S bloqueante directamente en el hilo del bucle.
¿Qué pasa con gevent?
Modelo heredado de "monkey-patching"; prefiere asyncio para desarrollos nuevos a menos que estés atascado en una pila síncrona.
¿Cuántos trabajadores en el pool de hilos?
Tamaño del pool de conexiones o llamadas bloqueantes concurrentes, no la cantidad de CPU.
¿Pool de procesos en tareas pequeñas?
Evítalo: la tarea debe amortizar el costo de creación y serialización ("pickle").
¿Cambia Polars/pandas la elección?
Las operaciones pesadas se ejecutan en código nativo, a menudo liberan el GIL; aún así, perfila, puede que permanezcan en hilos dentro del proceso.
¿Impacto de Python con hilos libres (Free-Threaded Python)?
Puede cambiar la elección de hilos para tareas limitadas por CPU; vuelve a ejecutar la lista de verificación después de auditar las extensiones.
¿Trabajadores de Redis/Celery?
Los trabajadores basados en procesos son una elección separada; a menudo son multiproceso por diseño para el aislamiento.
¿Dónde documentar?
ADR: carga de trabajo, perfil medido, modelo elegido, alternativas rechazadas, disparador de revisión.
Relacionado
- Conceptos Básicos de Concurrencia - terminología
- El GIL y Python con Hilos Libres - hilos de CPU
- Conceptos Básicos de Asyncio - ruta asíncrona
- Mejores Prácticas de Concurrencia - reglas de seguridad
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ y uv 0.6+.