实验 4:很多请求要限流
上一节知道了怎么让一批任务一起跑。现在加一个实际问题:如果有 1000 个 URL,能不能直接 gather() 全部打开?
通常不该这么做。原因很简单:
- 本机同时打开太多连接,容易打满文件描述符或连接池。
- 对方 API 可能有限流,瞬间打过去会被拒绝。
- 任务太多时,内存里会堆很多还没处理完的对象。
这一节用 asyncio.Semaphore 限制同时运行的请求数。
运行:
bash
python3 examples/asyncio_demos/04_limited_gather_semaphore.pySemaphore 怎么用
示例里最多允许 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 -= 1async 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 后面会从最小例子讲起。
小练习
- 把
Semaphore(3)改成Semaphore(1),观察输出是否变成接近串行。 - 改成
Semaphore(8),观察总耗时是否接近最慢的请求。 - 把
REQUESTS加到 20 个,确认输出里的active不会超过设定值。