实验 2:连续 await 和 gather
这一节先不讲限流,也不讲错误处理。只看一个最常见的差别:三个请求是一个接一个等,还是一起开始等。
运行:
bash
python3 examples/asyncio_demos/02_concurrent_sleep.py示例里的 fetch() 用 asyncio.sleep() 假装网络请求:
python
results = await asyncio.gather(
fetch("profile", 0.4),
fetch("orders", 0.2),
fetch("recommendations", 0.3),
)gather 适合什么
gather() 适合“这一批任务都要结果,全部结束后再往下走”的场景。它会把传入的 coroutine 安排成 Task,然后等待它们全部结束。
返回列表的顺序按传入顺序来,不按完成顺序来。下面这个例子里 orders 最先完成,但结果还是 profile、orders、recommendations。
总耗时接近 400ms,不是 900ms。因为三个等待同时开始了。
连续 await 为什么会变慢
如果你写成:
python
profile = await fetch("profile", 0.4)
orders = await fetch("orders", 0.2)
recommendations = await fetch("recommendations", 0.3)第二个请求会等第一个完成后才开始。代码里有 async 和 await,但这段还是串行。
create_task 也能做到
你也可以显式创建 Task:
python
profile_task = asyncio.create_task(fetch("profile", 0.4))
orders_task = asyncio.create_task(fetch("orders", 0.2))
recs_task = asyncio.create_task(fetch("recommendations", 0.3))
profile = await profile_task
orders = await orders_task
recs = await recs_task这也能并发,因为 Task 创建后就已经交给事件循环了。下一节会专门看这一点。
容易踩的坑
错误:
python
for url in urls:
result = await fetch(url)
results.append(result)如果每个 fetch 互相独立,这会把并发写成串行。
如果 URL 数量很少,可以这样改:
python
results = await asyncio.gather(*(fetch(url) for url in urls))先别急着把这段套到几千个 URL 上。数量很多时,要加并发上限。这个问题放到实验 4 单独讲。
小练习
- 把三个 delay 改成
1.0、0.1、0.1,观察总耗时。 - 把
gather改成连续 await,观察总耗时。 - 把示例改成 5 个
fetch(),确认总耗时接近最慢的那个等待。