Crear miles de corutinas no significa que una API, base de datos o pool soporte miles de operaciones simultáneas. asyncio.Semaphore mantiene permisos y hace esperar nuevas tareas cuando se alcanza el límite.

import asyncio

async def processar(item: int, limite: asyncio.Semaphore) -> int:
    async with limite:
        await asyncio.sleep(0.1)
        return item * 2

async def main() -> None:
    limite = asyncio.Semaphore(5)
    resultados = await asyncio.gather(
        *(processar(item, limite) for item in range(20))
    )
    print(resultados)

asyncio.run(main())

El bloque async with adquiere y libera el permiso de forma segura. Elige el valor según el recurso real, como tamaño del pool o límite documentado, y mide latencia, errores y backlog antes de aumentarlo.

Buenas prácticas

Un semáforo controla concurrencia instantánea, no peticiones por segundo. Para cuotas temporales usa una política específica. Deja dentro del bloque solo la operación limitada para no retener permisos durante trabajo local. Considera BoundedSemaphore si una liberación excesiva debe producir error.

Organiza tareas relacionadas con asyncio.TaskGroup. Para HTTP, alinea límites con el cliente descrito en HTTPX en Python.

La documentación oficial de primitivas de sincronización asyncio, consultada el 22 de julio de 2026, detalla la API y sus límites.