协作式多任务:主动交替执行
与传统操作系统通过强制切换进程(抢占式多任务)不同,Python 的 asyncio 采用了一种完全不同的方法,称为协作式多任务。
协作式多任务的工作原理
在协作式多任务中:
- 任务会一直运行,直到它们通过
await
主动让出控制权 - 没有自动的时间片切换或强制抢占
- CPU 密集型任务必须手动让出控制权,否则会阻塞其他任务
- 如果某个任务不配合,可能会阻塞整个系统
可以把它想象成一组人讨论问题,每个人都同意只在自然停顿时发言,然后让别人说话。当大家都遵守规则时,这种方式非常有效,但如果有人一直占用话语权,就会破坏整个系统。
抢占式 vs 协作式:关键区别
抢占式多任务(操作系统线程) | 协作式多任务(Asyncio) |
---|---|
操作系统强制中断任务 | 任务主动让出控制权 |
任务随时可能被中断 | 任务会一直运行直到遇到 await |
系统定时器触发上下文切换 | 等待操作时触发切换 |
适合 CPU 密集型任务 | 最适合 I/O 密集型任务 |
需要复杂的同步机制 | 同步更简单 |
任务切换自动发生 | 程序员需手动添加 await 点 |
篮球类比
想象一场篮球比赛:
- 抢占式多任务 就像有一个投篮计时器——时间一到,无论你在做什么,裁判(操作系统)都会强制收回球权
- 协作式多任务 则像是街头篮球,靠自觉——大家投完篮或无法推进时主动传球
第二种方式只要大家都遵守规则就很顺畅,但只要有一个自私的球员,比赛就会变得糟糕。
为什么 Python 选择协作式多任务
协作式多任务有几个优势:
- 简单 —— 不需要锁等复杂的同步原语
- 高效 —— 没有频繁的上下文切换带来的开销
- 可预测 —— 任务在明确的点让出控制权
- 单线程 —— 避免了许多线程相关的 bug 和竞态条件
缺点是程序员需要更加注意代码何时、何地让出控制权。
使用 Asyncio 时需要注意的事项
由于 asyncio 依赖于任务间的协作,以下实践非常重要:
- 使用
aiofiles.open()
替代普通的open()
,避免阻塞事件循环 - 进行 HTTP 操作时用
aiohttp
替代requests
- 延时操作时用
asyncio.sleep()
替代time.sleep()
- 在 CPU 密集型操作中定期让出控制权:
|
|
- 对于真正的 CPU 密集型任务,建议使用线程池:
|
|
理解并尊重 asyncio 的协作本质,你就能构建出高效且并发性强的应用程序,而无需传统多线程的复杂性。