一、Python协程概念
1. 基本定义
协程是Python中通过async/await
语法实现的轻量级并发模型,其本质是异步编程的入口。每个协程函数以async def
定义,内部通过await
表达式主动暂停执行并交还控制权。
2. 设计目的
解决传统线程在I/O密集型场景下的性能瓶颈:
- 高并发场景:1个CPU核可同时管理成千上万个协程(如处理10万+ HTTP请求)
- 资源高效:协程切换成本仅为线程的1/100(数据来源:Python官方性能报告)
3. 关键特性
- 单线程内并发:依赖
asyncio
事件循环在单线程中调度 - 非阻塞I/O:通过
await
将I/O操作交给底层异步驱动(如aiohttp) - 生成器继承:协程本质是生成器的增强版(
async def
→AsyncGenerator
)
二、Python协程本质
1. 底层机制
- 生成器 + 自动恢复:
async def
编译为生成器函数,await
等同于yield from
- 状态机管理:每次
await
将协程状态保存到堆栈中,恢复时从断点继续 - 事件循环驱动:
asyncio.get_event_loop()
持续监控协程状态,调度就绪任务
2. 与传统线程对比
特性 | 线程 | 协程 |
---|---|---|
调度权 | 操作系统内核 | 用户态(Python解释器) |
上下文切换 | 寄存器+内核栈(10-20μs) | 生成器状态(0.1-1μs) |
并发模型 | M:N(多核并行) | 1:N(单核轮转) |
适用场景 | CPU密集型任务 | I/O密集型任务 |
3. 协程运行流程
import asyncio
async def foo():
print("Start foo")
await asyncio.sleep(1) # 模拟I/O操作
print("End foo")
async def main():
await asyncio.gather(foo(), foo())
asyncio.run(main())
- 事件循环启动:
asyncio.run()
创建循环并运行main()
- foo()执行:打印"Start foo"后触发
await asyncio.sleep(1)
- 控制权移交:sleep返回
Future
对象,事件循环切换到其他任务 - I/O完成回调:1秒后sleep结束,事件循环重新调度
foo()
继续执行
三、生活化类比:餐厅服务员模型
1. 场景设定
一家网红餐厅同时服务1000桌客人,每桌客人点菜、等餐、上菜需要不同时间。
2. 传统线程模式
- 服务员:每个服务员独立处理一桌客人(类似线程)
- 问题:服务员需全程跟进客人(如等待厨房出餐),导致空闲等待
- 结果:1000桌需1000个服务员,人力成本极高
3. 协程模式
- 服务员 + 菜单传递员:
- 服务员(协程):接收点菜订单后,将菜单交给后台(
await
) - 后台厨师(异步I/O):处理订单并通知服务员(
Future
完成) - 服务员在等待期间可处理新订单(高效利用时间)
- 服务员(协程):接收点菜订单后,将菜单交给后台(
- 优势:1个服务员(主线程)+ N个后台厨师(异步任务)即可服务无限客户
4. 关键对应关系
餐厅元素 | 协程概念 |
---|---|
服务员 | 协程函数(async def) |
点菜订单 | await 表达式 |
后厨处理 | 异步I/O操作(如aiohttp) |
菜单传递员 | 事件循环(asyncio) |
四、核心语法与API详解
1. async/await
- async:标记函数为协程(生成器函数增强版)
- await:只能出现在
async
函数内,用于等待awaitable
对象 - 语法要求:
async def coroutine_name(): await some_awaitable_object()
2. awaitable对象
- 三种类型:
- 协程对象:
async def
定义的函数 - Future对象:表示异步操作的最终结果(如
asyncio.Future()
) - 任务对象:包装协程的
asyncio.Task()
(用于并发执行)
- 协程对象:
3. asyncio.wait(fs)
- 作用:批量等待多个异步任务完成
- 参数:
fs
可以是Iterable[awaitable]
或Future
- 返回值:
done, pending = asyncio.wait(fs)
(两个集合) - 示例:
async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() async def main(): urls = ["https://example.com", "https://google.com"] tasks = [fetch(url) for url in urls] done, pending = await asyncio.wait(tasks) # 等待所有任务完成 print(f"成功获取 {len(done)} 个页面")
4. 常用API总结
函数/类 | 用途 | 示例 |
---|---|---|
asyncio.run() | 启动事件循环并运行最高层协程 | asyncio.run(main()) |
asyncio.create_task() | 将协程包装为Task并加入调度队列 | task = asyncio.create_task(fetch(url)) |
asyncio.gather() | 并发执行多个协程并收集结果 | results = await asyncio.gather(*tasks) |
asyncio.sleep() | 非阻塞式等待(模拟I/O延迟) | await asyncio.sleep(1) |
五、注意事项与常见误区
-
阻塞操作禁忌
协程中禁止使用同步I/O函数(如time.sleep()
、requests.get()
),会阻塞整个事件循环:# ❌ 错误示例(阻塞式sleep) async def bad_example(): await asyncio.sleep(1) # 正确用法 time.sleep(1) # 阻塞事件循环!
-
CPU密集型任务处理
协程不适合计算密集型任务,需结合线程池:async def compute(): result = await asyncio.run_in_executor(None, cpu_bound_function())
-
异常处理
需显式捕获协程中的异常,否则会导致未处理的CancelledError
:try: await some_task() except asyncio.CancelledError: print("任务被取消")
六、进阶实战:异步爬虫
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["https://example.com" for _ in range(100)]
tasks = [fetch(url) for url in urls]
responses = await asyncio.gather(*tasks)
print(f"抓取完成,共获取 {len(responses)} 页数据")
# 运行爬虫
asyncio.run(main())
效果:单线程并发抓取100个页面,耗时约2-3秒(相比同步爬虫的100+秒)