asyncio调度介绍
概述
Python 的 asyncio
库提供了一种基于事件循环的异步编程模型,其核心是无栈协程(Stackless Coroutine)。无栈协程具有以下特性:
- 单线程事件循环:
asyncio
在单线程上创建一个事件循环,用于调度协程的执行。 - 协作式多任务:协程只有在主动交出控制权(如通过
await
)时,事件循环才能调度其他协程。 - 异步等待:使用
await
调用可调度的对象(如协程、Future
、Task
),并获取其结果。
事件循环
事件循环是 asyncio
的核心组件,负责管理和调度所有异步任务。其工作原理如下:
- 检查调度堆:将到期的定时任务移动到就绪堆。
- 处理 I/O 事件:通过
select
系统调用监听 I/O 事件,将就绪的 I/O 任务移动到就绪堆。 - 执行就绪任务:从就绪堆中取出任务并执行。
事件循环通过不断重复上述步骤,实现异步任务的调度和执行。
调度的对象
在 asyncio
中,协程可以调度的对象包括以下几种:
- 协程(Coroutine):
- 使用
async def
定义的函数,可以通过await
暂停和恢复执行。 - 协程本身是一个可等待对象,但不能直接被调度,需要封装为
Task
。
- 使用
- Future:
- 表示一个异步操作的最终结果。
- 通常由低级 API 使用,开发者更多使用
Task
。
- Task:
- 是
Future
的子类,封装了一个协程。 - 代表一个正在运行的协程,事件循环会调度其执行。
- 是
这些对象都可以通过 await
进行等待调用。
调度结构
事件循环通过两个核心数据结构管理协程任务:就绪堆和调度堆。
就绪堆(Ready Queue)
- 作用:存储当前可以立即执行的协程任务。
- 特点:
- 任务按 FIFO(先进先出)的顺序执行。
- 当协程遇到
await
并暂停时,事件循环会从就绪堆中取出下一个任务执行。
- 加入时机:
- 使用
call_soon()
或call_soon_threadsafe()
时,协程会加入就绪堆。 - I/O 事件完成或定时任务到期时,相关任务会加入就绪堆。
- 使用
调度堆(Scheduled Heap)
- 作用:存储需要延迟执行的协程任务,通常用于定时任务。
- 特点:
- 任务按时间戳排序,堆顶的任务是下一个即将到期的任务。
- 事件循环会定期检查调度堆,将到期的任务移动到就绪堆。
- 加入时机:
- 使用
asyncio.sleep()
或loop.call_later()
等延迟操作时,协程会被添加到调度堆。
- 使用
调度时机
协程被添加到调度堆或就绪堆的常见方式包括:
- **
asyncio.sleep()
**:- 当协程调用
await asyncio.sleep(delay)
时,事件循环会将协程封装为一个定时任务,并添加到调度堆中,等待delay
时间后执行。
- 当协程调用
- **
loop.call_later()
**:- 通过
loop.call_later(delay, callback)
,事件循环会将回调函数封装为定时任务,并添加到调度堆中。
- 通过
- **
loop.call_at()
**:- 通过
loop.call_at(when, callback)
,事件循环会将回调函数封装为定时任务,并添加到调度堆中,在指定时间when
执行。
- 通过
- **
asyncio.create_task()
**:- 使用
asyncio.create_task()
创建任务后,任务会在下一次await
时自动添加到就绪堆。
- 使用
- 协程嵌套调用:
- 当一个协程 A 中
await
另一个协程 B 时,协程 A 会通过add_done_callback
方法注册一个唤醒回调。当协程 B 完成时,事件循环会唤醒协程 A 并将其加入就绪堆。
- 当一个协程 A 中
总结
asyncio
的调度机制基于事件循环和协程,通过就绪堆和调度堆管理任务的执行顺序。其核心特点包括:
- 协作式调度:协程通过
await
主动交出控制权,事件循环根据任务状态调度执行。 - 高效 I/O 处理:通过
select
系统调用监听 I/O 事件,避免阻塞。 - 灵活的任务管理:支持定时任务、嵌套协程调用等多种调度方式。
理解 asyncio
的调度机制有助于编写高效、可维护的异步代码,充分利用 Python 的异步编程能力。
参考资料
- Python 官方文档: https://docs.python.org/3/library/asyncio.html
- Asyncio 源码: https://github.com/python/cpython/tree/main/Lib/asyncio