2. 并发执行多个协程
任务说明
本案例要求使用 asyncio.gather 并发运行多个协程,通过观察执行顺序和总耗时,理解并发与并行的区别。我们将创建三个模拟耗时操作的协程,每个协程内部通过 asyncio.sleep 模拟 I/O 等待,然后使用 gather 同时启动它们,记录总耗时并与串行执行对比。
核心实现
import asyncio
import time
async def worker(name: str, delay: float) -> str:
"""模拟一个耗时协程:等待 delay 秒后返回结果"""
print(f"协程 {name} 开始,预计等待 {delay} 秒")
await asyncio.sleep(delay)
print(f"协程 {name} 完成")
return f"{name} 的结果"
async def main():
# 记录开始时间
start = time.perf_counter()
# 并发执行三个协程
results = await asyncio.gather(
worker("A", 2),
worker("B", 1),
worker("C", 3)
)
# 计算总耗时
elapsed = time.perf_counter() - start
print(f"并发执行结果: {results}")
print(f"并发总耗时: {elapsed:.2f} 秒")
# 对比:串行执行同样三个协程
start = time.perf_counter()
serial_results = []
for name, delay in [("A", 2), ("B", 1), ("C", 3)]:
serial_results.append(await worker(name, delay))
elapsed_serial = time.perf_counter() - start
print(f"串行执行结果: {serial_results}")
print(f"串行总耗时: {elapsed_serial:.2f} 秒")
# 输出对比结论
print(f"\n耗时对比:并发 {elapsed:.2f}s vs 串行 {elapsed_serial:.2f}s")
print("并发总耗时约等于最慢协程的耗时(3秒),而非三者之和(6秒)")
if __name__ == "__main__":
asyncio.run(main())
执行流程说明
代码中 worker 协程通过 asyncio.sleep 模拟 I/O 等待(如网络请求、文件读写)。asyncio.gather 接收三个协程对象,将它们同时调度到事件循环中。由于 sleep 会主动让出控制权,三个协程的等待时间在事件循环中重叠推进,因此总耗时取决于最慢的协程(C,3秒),而非三者耗时之和。
串行对比部分使用普通 for 循环依次 await 每个协程,前一个完成后才启动下一个,因此总耗时为三者之和(6秒)。通过对比可以直观看到:并发让多个 I/O 等待重叠,但并未真正同时执行计算任务——这正是并发(交替执行)与并行(多核同时执行)的核心区别。
运行与验证
- 保存代码为
gather_demo.py,在 Python 3.7+ 环境运行:python gather_demo.py - 观察输出中的执行顺序:三个协程的“开始”打印会连续出现(几乎同时),而“完成”打印按各自延迟时间先后出现(B先于A,A先于C)。
- 判定标准:
- 并发总耗时约为 3 秒(最慢协程耗时),串行总耗时约为 6 秒;
- 若并发耗时接近 6 秒,说明协程未真正并发(可能误用了同步阻塞操作);
- 若“开始”打印不是连续出现,而是逐个间隔出现,说明 gather 未正确并发调度。
预期输出
协程 A 开始,预计等待 2 秒
协程 B 开始,预计等待 1 秒
协程 C 开始,预计等待 3 秒
协程 B 完成
协程 A 完成
协程 C 完成
并发执行结果: ['A 的结果', 'B 的结果', 'C 的结果']
并发总耗时: 3.00 秒
协程 A 开始,预计等待 2 秒
协程 A 完成
协程 B 开始,预计等待 1 秒
协程 B 完成
协程 C 开始,预计等待 3 秒
协程 C 完成
串行执行结果: ['A 的结果', 'B 的结果', 'C 的结果']
串行总耗时: 6.00 秒
耗时对比:并发 3.00s vs 串行 6.00s
并发总耗时约等于最慢协程的耗时(3秒),而非三者之和(6秒)
注意:上述输出为基于代码逻辑的确定性预期。实际运行中,perf_counter 的精度和系统调度可能带来毫秒级误差,但并发约 3 秒、串行约 6 秒的对比关系稳定成立。三个协程的“开始”打印在并发模式下会连续出现(间隔极小),而“完成”打印按延迟时间排序出现。