concurrent.futures
concurrent.futures proporciona pools de Executor de alto nivel con objetos Future para resultados asíncronos. ThreadPoolExecutor maneja operaciones de I/O superpuestas; ProcessPoolExecutor paraleliza el trabajo de CPU.
Receta
from concurrent.futures import ThreadPoolExecutor, as_completed
def task(n: int) -> int:
return n + 1
with ThreadPoolExecutor(max_workers=4) as ex:
futures = [ex.submit(task, i) for i in range(10)]
for fut in as_completed(futures):
print(fut.result())Cuándo usar esto:
- Llamadas de bloqueo de dispersión (fan-out) (HTTP, DB, archivos)
- Pools de CPU sin gestión manual de procesos
mapcon tiempo de espera y fragmentación (chunking)- Cancelación de grupos de trabajo al cerrar (shutdown)
- Integración con
asyncio.wrap_future
Ejemplo funcional
import time
from concurrent.futures import ProcessPoolExecutor, wait, FIRST_COMPLETED
def slow_square(n: int) -> int:
time.sleep(0.05)
return n * n
if __name__ == "__main__":
with ProcessPoolExecutor(max_workers=2) as ex:
futures = [ex.submit(slow_square, i) for i in range(6)]
done, not_done = wait(futures, return_when=FIRST_COMPLETED)
print("primero", done.pop().result())
for fut in not_done:
fut.cancel()Lo que esto demuestra:
waitconFIRST_COMPLETEDpara resultados parcialescancelcon mejor esfuerzo en tareas aún no iniciadas- El pool de procesos necesita el guardián principal (
if __name__ == "__main__":) y objetivos serializables (picklable) - El gestor de contexto espera a las tareas en ejecución al salir (a menos que se cancelen)
Análisis detallado
Superficie de la API
| Llamada | Comportamiento |
|---|---|
submit(fn, *args) | Future individual |
map(fn, iterable) | Iterador ordenado como la entrada |
as_completed(futures) | Produce resultados a medida que cada uno finaliza |
future.result(timeout=) | Bloquea o lanza TimeoutError |
Errores comunes
- Bloqueo en
result()sin tiempo de espera - detiene el cierre (shutdown). Solución: tiempos de espera y política de cancelación. - Excepción en el trabajador - se almacena en el Future, se lanza al llamar a
result(). Solución: manejar o registrar por cada future. - Tareas muy pequeñas en el pool de procesos - el costo de inicio (spawn) domina. Solución: agrupar elementos de trabajo (batch work items).
- Executor global compartido - complica las pruebas y la gestión del ciclo de vida. Solución: inyectar el executor, cerrarlo al apagar la aplicación.
- Pool de hilos para CPU - el GIL limita la aceleración. Solución:
ProcessPoolExecutor.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
asyncio.gather | I/O asíncrono nativo | SDK bloqueante |
multiprocessing.Pool | Código heredado | Greenfield prefiere futures |
joblib / dask | Paralelismo de big data | Scripts simples |
Preguntas frecuentes
¿Pool de hilos vs. pool de procesos?
Hilos para bloqueo de I/O; procesos para funciones de Python intensivas en CPU.
¿map vs. submit?
map preserva el orden y agrupa; submit ofrece control granular por tarea.
¿Cómo se propagan las excepciones?
Se lanzan al llamar a result() en el Future fallido.
¿shutdown `wait=False`?
Devuelve inmediatamente; las tareas en ejecución continúan en segundo plano - usar con cuidado al salir.
¿Integración con asyncio?
asyncio.wrap_future une a un objeto awaitable en código mixto.
¿Valor predeterminado de `max_workers`?
min(32, cpu_count + 4) para hilos - ajustar según la carga de trabajo.
¿Puedo usar `initializer`?
Sí, en ambos tipos de executor para configuración por trabajador (conexiones de DB - cuidado con la seguridad de procesos).
¿Son seguros para hilos los callbacks?
add_done_callback se ejecuta en un hilo arbitrario - mantener los callbacks cortos.
¿Cómo probar?
Inyectar un executor con max_workers=1 para determinismo en pruebas unitarias.
¿GIL y `ProcessPoolExecutor`?
Cada proceso tiene su propio GIL - ejecución de bytecode verdaderamente paralela.
Relacionado
- threading - primitivas subyacentes
- multiprocessing - procesos de bajo nivel
- Sync/Async Bridging - executor predeterminado
- Choosing a Concurrency Model - elegir el tipo de pool
Versiones de la pila: Esta página se escribió 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+.