asyncio.gather khởi chạy tất cả coroutine cùng lúc.
- Với 200 lời gọi, phía bên kia dễ trả 429, kết nối bị nghẽn, và nếu vài request đồng thời cùng làm vậy thì tiến trình mở hàng nghìn socket.
- Cách chuẩn là giới hạn bằng
asyncio.Semaphore(bounded concurrency).
python
import asyncio, httpx
async def fetch_all(urls: list[str], limit: int = 10) -> list[dict]:
sem = asyncio.Semaphore(limit)
async with httpx.AsyncClient(timeout=5) as client:
async def one(url: str) -> dict | None:
async with sem: # at most `limit` in flight
r = await client.get(url)
return r.json()
return await asyncio.gather(*(one(u) for u in urls), return_exceptions=True)Ba điểm cần nhớ:
- Dùng chung một AsyncClient cho cả batch để tái sử dụng connection pool; tạo client mới cho từng request là lỗi phổ biến làm chậm gấp nhiều lần.
- return_exceptions=True để một URL hỏng không huỷ cả batch; nếu để mặc định, gather ném exception đầu tiên và các task còn lại vẫn chạy nền.
- Đặt timeout ở tầng client, nếu không một endpoint treo sẽ giữ slot semaphore vô hạn.
Với Python 3.11+, asyncio.TaskGroup là lựa chọn tốt hơn khi muốn "một task lỗi thì huỷ cả nhóm", nhưng vẫn cần semaphore để chặn số lời gọi đồng thời.