Prácticas recomendadas de concurrencia
Reglas de seguridad y operabilidad para hilos, procesos y puentes a código asíncrono.
Cómo usar esta lista
- Aplícala antes de aumentar
max_workerso la distribución de tareas. - Realiza pruebas de carga en las rutas de apagado: las condiciones de carrera aparecen en el despliegue/reinicio.
- Prefiere el paso de mensajes; trata la memoria compartida como culpable hasta que se demuestre que es segura.
A - Selección de modelo
- Perfilar antes de elegir asyncio, hilos o procesos. Las etiquetas engañan sin datos de tiempo.
- Mantén el trabajo de CPU fuera del hilo del bucle de eventos de asyncio. Usa
to_threado un grupo de procesos. - Usa
ProcessPoolpara Python puro limitado por CPU en compilaciones con GIL. No esperes una aceleración lineal con hilos. - Dimensiona los grupos de hilos según los límites de concurrencia de E/S. Grupos de conexiones, no
cpu_count() * 5por defecto. - Documenta la elección del modelo en el ADR. Los futuros mantenedores necesitan el porqué.
B - Estado compartido
- Prefiere colas a estructuras mutables compartidas. El productor/consumidor simplifica el razonamiento.
- Protege los invariantes de varios pasos con bloqueos. Las operaciones de un solo bytecode no son transacciones completas.
- Adquiere bloqueos en un orden global fijo. Documenta el orden para evitar interbloqueos.
- Evita los singleton globales mutados por los trabajadores. Pasa contexto explícito o usa
threading.local(). - Usa mensajes inmutables entre etapas. Instantáneas, no grafos de objetos en vivo.
C - Procesos y grupos
- Protege la entrada de
multiprocessingconif __name__ == "__main__". Requerido en plataformas de "spawn". - Mantén las funciones de destino de los trabajadores como funciones de nivel superior serializables. No uses lambdas en
ProcessPool. - Establece
maxtasksperchildpara extensiones C con fugas de memoria. Los grupos de larga duración reciclan los trabajadores. - Limita los trabajadores según el presupuesto de memoria. RSS × trabajadores debe caber en el límite del contenedor.
- Configura el registro en el inicializador del trabajador. Evita manejadores rotos heredados.
D - Apagado y errores
- Une los hilos con tiempo de espera durante el apagado. Registra los rezagados; evita que los demonios actúen en medio de una sección crítica.
- Usa centinelas de cola por consumidor. Una "píldora de veneno" por hilo/proceso trabajador.
- Llama a
task_donepor cadaqueue.geten pipelines dethreading.Queue. De lo contrario,joinse colgará. - Maneja las excepciones de
Futureenresult(). Los futuros silenciosos que fallan pierden los errores. - Cancela los futuros pendientes al agotar el tiempo de espera. No dejes trabajo en segundo plano sin control.
E - Puente asíncrono
- Nunca llames a E/S bloqueante dentro de corrutinas sin un ejecutor. Bloquea todas las tareas.
- Comparte un bucle por hilo. No llames a
asyncio.runrepetidamente en servidores activos; usalifespan. - Propaga
contextvarsa los trabajadores de hilos cuando sea necesario. IDs de solicitud para registros. - Prueba el código concurrente bajo estrés. Las pruebas basadas solo en tiempos fallan; usa barreras/eventos.
- Revisa el modelo de concurrencia cuando las dependencias se vuelvan asíncronas. Los grupos de hilos pueden volverse innecesarios.
Preguntas frecuentes
¿Causa principal de incidentes de producción?
Caché mutable compartida sin bloqueos más trabajadores concurrentes: usa colas o sincronización adecuada.
¿Cuántos procesos?
Empieza con cpu_count(), reduce si la memoria por trabajador es grande.
¿Es asyncio siempre más seguro?
Más seguro contra condiciones de carrera en un solo hilo, pero bloquear el bucle causa una interrupción total: un modo de fallo diferente.
¿Debería usar `threading.local`?
Para estado por solicitud en servidores WSGI/hilo síncronos, no es un sustituto de contextvars asíncronos adecuados en ASGI.
¿Cómo depurar interbloqueos?
Vuelca todas las pilas de hilos (faulthandler.dump_traceback_all) durante la reproducción de cuelgues en staging.
¿Son suficientes los enteros atómicos?
itertools y operator no arreglarán invariantes de negocio compuestos: usa bloqueos o propiedad de un solo hilo.
¿Grupo de procesos en una aplicación asíncrona?
loop.run_in_executor(process_pool, fn): vincula el ciclo de vida del grupo al inicio/apagado de la aplicación.
¿Legado de Gevent?
Mantén si es necesario; FastAPI/asyncio nuevos evitan el monkey-patching de la biblioteca estándar.
¿Enfoque de prueba de carga?
Apagado, encolamiento de ráfagas y contrapresión del consumidor lento, no solo rendimiento en estado estable.
¿Despliegue con hilos libres?
Reaudita todas las prácticas: las condiciones de carrera en dict/list son más probables sin la serialización del GIL.
Relacionado
- Elección de un modelo de concurrencia - elegir modelo
- threading - detalles de bloqueos
- Colas y Productor/Consumidor - pipelines
- Prácticas recomendadas de Async - reglas específicas de async
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+.