Python asyncio 异步编程入门
·
【保姆级教程】Python asyncio 异步编程入门:从烧开水洗水果理解并发原理
一、前言
在日常开发中,我们经常会遇到需要处理耗时任务(比如网络请求、文件IO、等待硬件响应等)的场景。如果用传统的同步编程方式,程序会卡在耗时任务处等待,效率极低。
Python 的 asyncio 库为我们提供了异步编程能力,能让程序在等待耗时任务时「切换」去做其他事,从而提升整体执行效率。本文将通过「烧开水+洗水果」这个生活化的例子,带你从零理解异步编程的核心逻辑和并发原理。
二、同步 vs 异步:效率对比
先看一个直观的对比:
- 同步执行:先烧开水(5秒),等水烧开了再洗水果(2秒),总共耗时 7秒。
- 异步并发执行:烧开水的同时洗水果,总共耗时 5秒(取最长任务的时间)。
下面我们用代码实现这个场景,并拆解背后的原理。
三、完整代码实现
import asyncio
# 定义异步函数(协程):模拟烧开水(耗时5秒)
async def boil_water():
print("开始烧开水...")
# 模拟耗时操作,await 表示「等待但不阻塞事件循环」
await asyncio.sleep(5)
print("水烧开了!")
return "开水"
# 定义异步函数(协程):模拟洗水果(耗时2秒)
async def wash_fruits():
print("开始洗水果...")
await asyncio.sleep(2)
print("水果洗好了!")
return "干净的水果"
# 主协程:协调所有异步任务
async def main():
# 方式1:串行执行(效率低,仅对比)
# water = await boil_water() # 等5秒
# fruits = await wash_fruits() # 再等2秒,总计7秒
# 方式2:异步并发执行(核心)
# 1. 创建任务对象:将协程加入事件循环的待执行队列
task1 = asyncio.create_task(boil_water())
task2 = asyncio.create_task(wash_fruits())
# 2. 等待所有任务完成(期间事件循环会自动切换任务)
water = await task1
fruits = await task2
print(f"\n准备好的材料:{water}, {fruits}")
# 启动异步程序
if __name__ == "__main__":
# asyncio.run():创建事件循环 → 运行主协程 → 关闭事件循环
asyncio.run(main())
代码运行结果
开始烧开水...
开始洗水果...
水果洗好了!
水烧开了!
准备好的材料:开水, 干净的水果
可以看到:洗水果(2秒)和烧开水(5秒)是同时进行的,总耗时仅5秒。
四、异步并发的核心原理(重点)
要理解 asyncio 的并发,关键要搞懂 3 个核心概念:协程(Coroutine)、事件循环(Event Loop)、await/async。
1. 核心概念拆解
| 概念 | 通俗解释 | 代码对应 |
|---|---|---|
| 协程(Coroutine) | 可暂停、可恢复的「特殊函数」,用 async def 定义 | boil_water()、wash_fruits() |
| 事件循环(Event Loop) | 异步编程的「总指挥」,负责调度所有协程的执行 | asyncio.run() 自动创建并启动 |
| await | 「暂停当前协程」,将控制权交还给事件循环,等待指定任务完成后再恢复 | await asyncio.sleep(5) |
| asyncio.create_task() | 将协程「注册」到事件循环,使其进入「待执行/执行中」状态 | task1 = asyncio.create_task(boil_water()) |
2. 并发执行的底层流程(文本图示版,完整流程)
┌─────────────────────────────────────────────────────────────────┐
│ 阶段1:启动程序,创建事件循环 │
│ asyncio.run(main()) → 创建事件循环 → 开始执行main协程 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 阶段2:创建异步任务,加入事件循环 │
│ main协程中执行: │
│ task1 = asyncio.create_task(boil_water()) → 烧开水任务入队 │
│ task2 = asyncio.create_task(wash_fruits()) → 洗水果任务入队 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 阶段3:事件循环调度task1(烧开水)执行 │
│ 1. 执行boil_water() → 打印「开始烧开水...」 │
│ 2. 遇到await asyncio.sleep(5): │
│ - 暂停task1(烧开水协程) │
│ - 把控制权交还给事件循环 │
│ - 标记task1需要等待5秒后唤醒 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 阶段4:事件循环切换到task2(洗水果)执行 │
│ 1. 执行wash_fruits() → 打印「开始洗水果...」 │
│ 2. 遇到await asyncio.sleep(2): │
│ - 暂停task2(洗水果协程) │
│ - 把控制权交还给事件循环 │
│ - 标记task2需要等待2秒后唤醒 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 阶段5:事件循环进入「等待」状态(无可执行的协程) │
│ 等待2秒后 → task2的sleep(2)完成,事件循环唤醒task2 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 阶段6:恢复task2执行,洗水果完成 │
│ 1. 继续执行wash_fruits()剩余代码 → 打印「水果洗好了!」 │
│ 2. task2执行完毕,返回「干净的水果」 │
│ 3. 事件循环继续等待(还需等3秒,因为task1总共要等5秒) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 阶段7:等待3秒后,事件循环唤醒task1 │
│ 1. 继续执行boil_water()剩余代码 → 打印「水烧开了!」 │
│ 2. task1执行完毕,返回「开水」 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 阶段8:main协程获取结果,程序结束 │
│ 1. await task1 获取「开水」,await task2 获取「干净的水果」 │
│ 2. 打印最终结果,main协程执行完毕 │
│ 3. 事件循环关闭,程序退出 │
└─────────────────────────────────────────────────────────────────┘
3. 关键结论
- 异步并发的核心是:事件循环在协程遇到
await(耗时操作)时,会暂停当前协程,切换到其他可执行的协程,避免CPU空等。 asyncio.sleep()不是真的「睡眠」,而是模拟「耗时操作」,它会主动把控制权交还给事件循环。- 只有耗时的IO操作(网络请求、文件读写、sleep等)才能体现异步的优势;纯计算型任务(如循环加和)用异步反而没效果(因为没有
await让协程暂停)。
五、常见误区澄清
- 异步 ≠ 多线程:
- 异步是「单线程」下的任务切换,由事件循环调度;
- 多线程是「多线程并行」,由操作系统调度。
- await 不是阻塞:
- 阻塞:线程被挂起,CPU啥也不干;
- await:协程暂停,CPU去执行其他协程,效率更高。
- 必须用 async/await 配套:
- 用
async def定义的协程,必须通过await或asyncio.create_task()执行; - 普通函数不能直接
await,只有「可等待对象」(协程、Task、Future)才能被await。
- 用
六、实际应用场景
异步编程在以下场景能大幅提升效率:
- 网络爬虫:同时请求多个网页,而非逐个等待响应;
- 后端接口:处理高并发的HTTP请求(如FastAPI框架基于asyncio);
- 数据处理:同时读取多个文件/数据库查询结果。
总结
- Python异步编程的核心是
asyncio事件循环调度协程,通过await实现任务切换,避免CPU空等耗时操作; asyncio.create_task()是实现并发的关键,能让多个协程同时进入事件循环执行;- 异步的优势体现在IO密集型任务(网络/文件/等待),而非计算密集型任务。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)