Yeda AI Tips · #124

English

Los threads no arreglan tu Python limitado por CPU

Los threads no van a acelerar tu Python limitado por CPU. Nunca. En CPython, el global interpreter lock (GIL) permite que solo un thread ejecute bytecode de Python a la vez — así que diez threads haciendo cálculos se turnan en un solo núcleo mientras los demás quedan ociosos. Y aun así, si le pides a un asistente de IA "paraleliza esto", agarra ThreadPoolExecutor por defecto, sin importar la carga de trabajo. La solución es una sola decisión: identifica el límite y elige el modelo.

La tabla que zanja la discusión

Tu trabajo es…EjemplosUsaPor qué funciona
Limitado por I/O, pocas llamadasalgunas peticiones HTTP, lectura de archivosThreadPoolExecutorEl GIL siempre se libera durante I/O, así que los threads sí solapan las esperas
Limitado por I/O, miles de conexionesscrapers, websockets, fan-out de APIsasyncioUn solo thread con event loop; la documentación lo llama "un ajuste perfecto para código de red estructurado y limitado por IO"
Limitado por CPUprocesamiento de imágenes, bucles de hashing, matemática pesada en Python puroProcessPoolExecutorProcesos separados, cada uno con su propio intérprete: "esquiva el Global Interpreter Lock"

La propia biblioteca estándar codifica esta división en los valores por defecto: ThreadPoolExecutor usa min(32, os.process_cpu_count() + 4) — más workers que núcleos, porque se espera que estén esperando — mientras que ProcessPoolExecutor usa exactamente os.process_cpu_count(), un proceso ocupado por núcleo.

El cambio de dos líneas

Ambos executors comparten la interfaz de concurrent.futures, así que corregir una mala elección suele ser editar una palabra:

from concurrent.futures import ProcessPoolExecutor  # was ThreadPoolExecutor

with ProcessPoolExecutor() as pool:          # one process per core
    results = list(pool.map(hash_block, blocks))

Mismo submit(), mismo map(), mismos futures. La advertencia: los pools de procesos serializan (pickle) argumentos y resultados entre procesos, así que pasa datos simples (bytes, dicts, dataclasses), no sockets abiertos ni lambdas.

Cómo saber qué límite estás enfrentando

No adivines: mide. Ejecuta la versión secuencial una vez:

time.process_time() frente a time.perf_counter() alrededor del bucle caliente lo responde en cuatro líneas. Esa es también la pregunta de code review cuando un asistente te entrega código con threads: "¿qué dijo el profiler sobre el límite?"

Notas para usuarios avanzados

Recursos

¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.

Habla con nosotros · Lee el blog