El GIL (Global Interpreter Lock) es un candado dentro de Python. Solo un thread puede tener el candado a la vez. Sin candado, no se ejecuta código Python. Por eso tus threads parecen paralelos, pero no lo son.
Queremos usar múltiples threads para ejecutar tareas en paralelo, pero Python no aprovecha múltiples núcleos en tareas de cómputo.
Visualizando el GIL
Imaginá el GIL como un único micrófono en una mesa redonda: hablan todos, pero solo el que tiene el micrófono puede hablar.
Python sí permite múltiples threads, pero el GIL hace que solo uno ejecute código Python a la vez dentro de un proceso.
Threads en CPU-bound: la ilusión del paralelismo
Cuando lanzás 4 threads a sumar números, el GIL los obliga a turnarse. Un solo núcleo trabaja, los otros tres miran.
El intérprete les pasa el "micrófono" (GIL) cada ~5 ms. Resultado: 4 threads tardan lo mismo que 4 ejecuciones en serie. A veces incluso un poco más por el overhead.
Procesos en CPU-bound: paralelismo real
Cada proceso es un intérprete Python independiente, con su propio GIL. Ahora sí, los 4 núcleos trabajan en paralelo.
Cada cuadrito tiene su propio 🔒. Speedup ~4x. Costo: más RAM y datos que viajan entre procesos por pickle.
El GIL no elimina los threads. Solo evita que se ejecuten en paralelo real cuando el trabajo es CPU-bound.
Threads en I/O-bound: acá sí brillan
Cuando un thread espera (red, disco, base de datos), suelta el GIL. Mientras espera, otro thread agarra el candado y avanza.
3 threads, 3 esperas en paralelo, 3x más rápido. Acá threading y asyncio son la herramienta correcta.
Regla práctica
Conclusión
El GIL es una decisión de diseño, no un bug: simplifica el intérprete y acelera el código de un solo thread. El precio es que los threads no paralelizan cómputo. Si tu cuello de botella es esperar (I/O), threading o asyncio. Si es calcular (CPU), multiprocessing. Esa única regla resuelve el 95% de los casos.