Skip to content

实验 4:很多请求要限流

上一节知道了怎么让一批任务一起跑。现在加一个实际问题:如果有 1000 个 URL,能不能直接 gather() 全部打开?

通常不该这么做。原因很简单:

  • 本机同时打开太多连接,容易打满文件描述符或连接池。
  • 对方 API 可能有限流,瞬间打过去会被拒绝。
  • 任务太多时,内存里会堆很多还没处理完的对象。

这一节用 asyncio.Semaphore 限制同时运行的请求数。

运行:

bash
python3 examples/asyncio_demos/04_limited_gather_semaphore.py

Semaphore 怎么用

示例里最多允许 3 个请求同时进入真正的 fetch 区域:

python
sem = asyncio.Semaphore(3)
active = 0

async def fetch(name, delay):
    nonlocal active

    async with sem:
        active += 1
        try:
            await asyncio.sleep(delay)
            return f"{name}: ok"
        finally:
            active -= 1

async with sem 可以理解成“拿一个名额”。名额满了,后面的 Task 会在这里等。等前面的 Task 退出 async with,名额归还,后面的 Task 才能进去。

active 只是为了打印当前正在跑的请求数,方便你确认它不会超过 3。真正起限制作用的是 Semaphore

gather 仍然可以用

加了限流以后,仍然可以用 gather() 收集所有结果:

python
results = await asyncio.gather(
    *(fetch(name, delay) for name, delay in REQUESTS)
)

区别在于:所有 Task 都创建了,但真正同时发请求的最多只有 3 个。

Semaphore 和 Queue 怎么选

先用这个粗略判断:

场景用什么
你已经有一批 URL,只想限制同时请求数量Semaphore
任务会不断产生,需要 worker 慢慢消费Queue
需要把生产速度也压下来,避免队列越堆越多Queue(maxsize=...)

这一节先用 Semaphore。Queue 后面会从最小例子讲起。

小练习

  1. Semaphore(3) 改成 Semaphore(1),观察输出是否变成接近串行。
  2. 改成 Semaphore(8),观察总耗时是否接近最慢的请求。
  3. REQUESTS 加到 20 个,确认输出里的 active 不会超过设定值。

面向学习目的的 Python asyncio 中文教程与 mini_asyncio 教学运行时。