El GIL de Python, o Global Interpreter Lock, es un mecanismo presente en la implementación tradicional de CPython que permite que un solo hilo ejecute bytecode de Python a la vez dentro de un proceso. Esta característica simplifica parte de la gestión interna de memoria, pero también limita el paralelismo de hilos en tareas intensivas de CPU.
El GIL no significa que Python no pueda ejecutar tareas concurrentes ni aprovechar varios núcleos. Significa que debes elegir la herramienta adecuada según el tipo de trabajo: threading para espera de entrada y salida, multiprocessing para cálculo paralelo, asyncio para muchas operaciones cooperativas y bibliotecas nativas para procesamiento vectorizado.
Qué protege el GIL
CPython utiliza conteo de referencias para administrar muchos objetos. El GIL evita que varios hilos modifiquen ciertas estructuras internas al mismo tiempo sin una coordinación mucho más compleja.
La documentación oficial del módulo threading de Python explica que, debido al GIL, solo un hilo puede ejecutar bytecode de Python a la vez en una compilación estándar de CPython.
El GIL no bloquea todo el programa
Cuando un hilo espera una operación de red, disco o sistema, CPython puede liberar el GIL para que otro hilo continúe. Por eso threading puede mejorar mucho aplicaciones con tareas de entrada y salida.
import threading
import time
def wait_for_service(name):
print(f"Iniciando {name}")
time.sleep(2)
print(f"Terminando {name}")
threads = [
threading.Thread(
target=wait_for_service,
args=(f"task-{index}",),
)
for index in range(4)
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()sleep() simula una espera. Los cuatro hilos pueden avanzar de manera solapada porque no están realizando cálculo de Python continuamente.
Tareas de CPU y tareas de I/O
- CPU-bound: compresión, cálculo numérico puro, transformaciones intensivas, búsqueda exhaustiva o procesamiento de imágenes escrito en Python.
- I/O-bound: solicitudes HTTP, lectura de archivos, consultas a bases de datos, espera de sockets o servicios externos.
La elección de concurrencia depende principalmente de esta diferencia.
Por qué varios hilos no aceleran siempre la CPU
def count_down(value):
while value > 0:
value -= 1Si dos hilos ejecutan esta función, deben alternarse para obtener el GIL. El cambio de contexto añade trabajo y el tiempo total puede ser igual o incluso peor que una ejecución secuencial.
La guía sobre scripts lentos en Python explica por qué conviene medir antes de cambiar la arquitectura.
Utilizar multiprocessing para cálculo paralelo
Cada proceso posee su propio intérprete y su propio GIL:
from concurrent.futures import ProcessPoolExecutor
def calculate_sum(limit):
return sum(number * number for number in range(limit))
if __name__ == "__main__":
limits = [4_000_000] * 4
with ProcessPoolExecutor() as executor:
results = list(
executor.map(calculate_sum, limits)
)
print(results)Este enfoque puede utilizar varios núcleos, pero tiene costes de creación de procesos, serialización y transferencia de datos. No es automáticamente mejor para trabajos pequeños.
ThreadPoolExecutor para I/O
from concurrent.futures import ThreadPoolExecutor
from urllib.request import urlopen
def download_size(url):
with urlopen(url, timeout=10) as response:
return len(response.read())
urls = [
"https://example.com",
"https://example.org",
]
with ThreadPoolExecutor(max_workers=4) as executor:
sizes = list(executor.map(download_size, urls))
print(sizes)Los hilos resultan apropiados porque gran parte del tiempo se dedica a esperar datos de red.
Asyncio como alternativa
asyncio ejecuta muchas tareas cooperativas en un hilo cuando las operaciones admiten espera asíncrona:
import asyncio
async def simulated_request(name):
await asyncio.sleep(1)
return f"{name} completada"
async def main():
results = await asyncio.gather(
simulated_request("A"),
simulated_request("B"),
simulated_request("C"),
)
print(results)
asyncio.run(main())La guía de asyncio en Python explica coroutines, tareas, await y manejo de errores.
Bibliotecas que liberan el GIL
Muchas bibliotecas escritas en C, C++ o Fortran liberan el GIL durante operaciones intensivas. NumPy, por ejemplo, puede ejecutar cálculos internos fuera del bytecode de Python:
import numpy as np
values = np.arange(10_000_000, dtype=float)
result = np.sqrt(values).sum()
print(result)Esto no significa que todas las funciones de una biblioteca liberen el GIL. Consulta la documentación y mide el caso concreto.
La guía de análisis de datos con NumPy y Pandas muestra operaciones vectorizadas que evitan bucles de Python.
Medir antes de optimizar
No atribuyas cualquier lentitud al GIL. El problema puede ser un algoritmo ineficiente, demasiada entrada y salida, llamadas repetidas, estructuras inadecuadas o consultas lentas.
python -m cProfile -s cumulative app.pyLa guía de cProfile en Python explica cómo encontrar las funciones que realmente consumen tiempo.
Ejemplo comparativo
from concurrent.futures import (
ProcessPoolExecutor,
ThreadPoolExecutor,
)
from time import perf_counter
def cpu_task(limit):
total = 0
for number in range(limit):
total += number * number
return total
def measure(executor_class, workers=4):
start = perf_counter()
with executor_class(max_workers=workers) as executor:
list(executor.map(cpu_task, [3_000_000] * workers))
return perf_counter() - start
if __name__ == "__main__":
print(
"Threads:",
measure(ThreadPoolExecutor),
)
print(
"Processes:",
measure(ProcessPoolExecutor),
)El resultado depende del sistema, del número de núcleos y del tamaño de la tarea. No uses una única ejecución como benchmark definitivo.
Compartir datos entre procesos
Los procesos no comparten memoria Python de forma predeterminada. Los argumentos y resultados deben serializarse. Objetos grandes pueden eliminar parte de la mejora.
Reduce la frecuencia de mensajes, procesa lotes y devuelve resultados compactos. Para datos muy grandes, considera memoria compartida, archivos mapeados o bibliotecas especializadas.
Seguridad con procesos en Windows
En Windows es imprescindible proteger el punto de entrada:
if __name__ == "__main__":
run_parallel_work()Sin esta condición, cada proceso hijo puede volver a crear procesos repetidamente.
Hilos y condiciones de carrera
El GIL no elimina todos los problemas de sincronización. Una operación compuesta puede intercalarse con otro hilo:
from threading import Lock
lock = Lock()
counter = 0
def increment():
global counter
with lock:
counter += 1Utiliza locks, colas o estructuras seguras cuando varios hilos modifican estado compartido. La sentencia with en Python también administra bloqueos como context managers.
Python sin GIL y versiones futuras
El proyecto de Python ha trabajado en compilaciones opcionales sin GIL. La PEP 703 describe una ruta para hacer opcional el GIL en CPython. La disponibilidad, compatibilidad y rendimiento dependen de la versión y de las extensiones utilizadas.
No diseñes una aplicación suponiendo que todos los entornos ejecutan una compilación sin GIL. Comprueba siempre la plataforma concreta.
Elegir la herramienta correcta
- Utiliza ejecución secuencial para trabajos pequeños y simples.
- Utiliza threads para espera de red, archivos o servicios.
- Utiliza asyncio para muchas operaciones asíncronas compatibles.
- Utiliza procesos para CPU intensiva escrita en Python.
- Utiliza NumPy u otras bibliotecas nativas para cálculo vectorizado.
- Utiliza un sistema de colas para trabajos distribuidos o duraderos.
Errores frecuentes
- Afirmar que Python no puede usar varios núcleos.
- Crear hilos para acelerar cualquier bucle.
- Usar procesos para tareas diminutas.
- Compartir objetos enormes entre procesos.
- Confundir concurrencia con paralelismo.
- Ignorar condiciones de carrera porque existe el GIL.
- Optimizar sin medir.
- Suponer que toda biblioteca nativa libera el GIL.
Conclusión
El GIL limita la ejecución simultánea de bytecode de Python dentro de un proceso tradicional de CPython, pero no impide concurrencia, procesos paralelos ni cálculo nativo. Clasifica primero la tarea como CPU-bound o I/O-bound. Después mide y elige threads, asyncio, multiprocessing o una biblioteca vectorizada según el cuello de botella real.






