Python Async vs Threads: Cuándo usar asyncio

Backend2026-09-11TryQuickToolBox

Por qué la concurrencia importa en Python

Tienes un script de Python que obtiene datos de múltiples APIs. Funciona, pero es lento: cada solicitud espera a que termine la anterior. Has oído hablar de asyncio y los threads, pero ¿cuál deberías usar? Elegir el modelo incorrecto puede llevar a un código innecesariamente complejo o dolorosamente lento.

Esta guía aclara la confusión. Compararemos los modelos async y threading de Python, te mostraremos exactamente cuándo usar cada uno y proporcionaremos ejemplos prácticos que puedes adaptar a tus propios proyectos.

Entendiendo el panorama de concurrencia en Python

Python ofrece tres modelos principales de concurrencia:

El GIL es un mutex que impide que múltiples hilos ejecuten bytecode de Python simultáneamente. Esto significa que los threads no aceleran tareas CPU-bound, pero sí ayudan con tareas I/O-bound porque el GIL se libera durante las operaciones de E/S.

Cuándo usar asyncio

asyncio brilla cuando tu programa pasa la mayor parte del tiempo esperando eventos externos: solicitudes de red, E/S de archivos, consultas a bases de datos o temporizadores. Debido a que usa un solo hilo, evitas la sobrecarga de creación de hilos y cambio de contexto. El bucle de eventos gestiona eficientemente miles de conexiones concurrentes.

Usa asyncio cuando:

Aquí tienes un ejemplo simple que obtiene múltiples URLs concurrentemente:

import asyncio
import aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = [
        'https://example.com',
        'https://example.org',
        'https://example.net',
    ]
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)
    print(f'Fetched {len(results)} pages')

asyncio.run(main())

Cuándo usar threads

Los threads son una buena opción cuando tienes operaciones de E/S bloqueantes que no tienen equivalentes async, o cuando trabajas con código heredado que no es compatible con async. También son más simples de razonar para un pequeño número de tareas.

Usa threads cuando:

Ejemplo usando concurrent.futures.ThreadPoolExecutor:

from concurrent.futures import ThreadPoolExecutor
import requests

def fetch(url):
    response = requests.get(url)
    return response.text

urls = [
    'https://example.com',
    'https://example.org',
    'https://example.net',
]

with ThreadPoolExecutor(max_workers=10) as executor:
    results = list(executor.map(fetch, urls))

print(f'Fetched {len(results)} pages')

Comparando asyncio y threads

Aquí tienes una comparación lado a lado para ayudarte a decidir:

Aspecto asyncio Threads
Modelo de concurrencia Bucle de eventos de un solo hilo Múltiples hilos del SO
Mejor para I/O-bound, alta concurrencia I/O-bound, llamadas bloqueantes
Rendimiento CPU-bound Pobre (GIL) Pobre (GIL)
Escalabilidad Miles de tareas Cientos de hilos
Complejidad Requiere sintaxis async/await Estilo síncrono familiar
Depuración Puede ser complicada con tracebacks Herramientas de depuración estándar
Ecosistema Soporte async creciente Universal

¿Qué pasa con las tareas CPU-bound?

Ni asyncio ni los threads te ayudarán a paralelizar trabajo intensivo en CPU en Python debido al GIL. Para tareas CPU-bound, como cálculo numérico, procesamiento de imágenes o compresión de datos, usa multiprocessing o concurrent.futures.ProcessPoolExecutor.

Ejemplo:

from concurrent.futures import ProcessPoolExecutor

def cpu_heavy(n):
    return sum(i * i for i in range(n))

if __name__ == '__main__':
    with ProcessPoolExecutor() as executor:
        results = list(executor.map(cpu_heavy, [10**6, 10**6, 10**6]))
    print(results)

Mejores prácticas y trampas

Preguntas frecuentes

¿Puedo usar asyncio y threads juntos?

Sí. Puedes ejecutar código bloqueante en un pool de threads usando asyncio.to_thread() o loop.run_in_executor(). Esto es útil cuando necesitas llamar a una librería síncrona desde código async sin bloquear el bucle de eventos.

¿asyncio funciona con el GIL?

Sí, asyncio se ejecuta en un solo hilo y está sujeto al GIL. Sin embargo, como está diseñado para tareas I/O-bound, el GIL se libera durante las operaciones de E/S, permitiendo que otras tareas se ejecuten. Para trabajo CPU-bound, asyncio no ofrece ninguna aceleración.

¿Qué es más rápido: asyncio o threads?

Para tareas I/O-bound con muchas operaciones concurrentes, asyncio es generalmente más rápido y escalable porque evita la sobrecarga de los hilos. Para un pequeño número de tareas bloqueantes, los threads pueden ser más simples y rendir de manera similar. Ninguno ayuda con tareas CPU-bound.

Tomando la decisión correcta

Comienza identificando si tu cuello de botella es E/S o CPU. Si es E/S y necesitas alta concurrencia, recurre a asyncio. Si es E/S pero estás lidiando con librerías bloqueantes o scripts más simples, los threads son una opción sólida. Para trabajo CPU-bound, usa multiprocessing.

Recuerda que a menudo puedes combinar estos modelos, por ejemplo, usando asyncio para operaciones de red y un pool de threads para E/S de archivos. La clave es entender las ventajas y desventajas y elegir la herramienta que se ajuste a tu problema específico.

Cuando necesites formatear o validar rápidamente datos JSON devueltos por tus llamadas API concurrentes, prueba nuestro JSON Formatter para imprimir y depurar con facilidad.