asyncio调度介绍

概述

Python 的 asyncio 库提供了一种基于事件循环的异步编程模型,其核心是无栈协程(Stackless Coroutine)。无栈协程具有以下特性:

  • 单线程事件循环asyncio 在单线程上创建一个事件循环,用于调度协程的执行。
  • 协作式多任务:协程只有在主动交出控制权(如通过 await)时,事件循环才能调度其他协程。
  • 异步等待:使用 await 调用可调度的对象(如协程、FutureTask),并获取其结果。

事件循环

事件循环是 asyncio 的核心组件,负责管理和调度所有异步任务。其工作原理如下:

  1. 检查调度堆:将到期的定时任务移动到就绪堆。
  2. 处理 I/O 事件:通过 select 系统调用监听 I/O 事件,将就绪的 I/O 任务移动到就绪堆。
  3. 执行就绪任务:从就绪堆中取出任务并执行。

事件循环通过不断重复上述步骤,实现异步任务的调度和执行。


调度的对象

在 asyncio 中,协程可以调度的对象包括以下几种:

  1. 协程(Coroutine)
    • 使用 async def 定义的函数,可以通过 await 暂停和恢复执行。
    • 协程本身是一个可等待对象,但不能直接被调度,需要封装为 Task
  2. Future
    • 表示一个异步操作的最终结果。
    • 通常由低级 API 使用,开发者更多使用 Task
  3. Task
    • 是 Future 的子类,封装了一个协程。
    • 代表一个正在运行的协程,事件循环会调度其执行。

这些对象都可以通过 await 进行等待调用。

调度结构

事件循环通过两个核心数据结构管理协程任务:就绪堆调度堆

就绪堆(Ready Queue)

  • 作用:存储当前可以立即执行的协程任务。
  • 特点
    • 任务按 FIFO(先进先出)的顺序执行。
    • 当协程遇到 await 并暂停时,事件循环会从就绪堆中取出下一个任务执行。
  • 加入时机
    • 使用 call_soon() 或 call_soon_threadsafe() 时,协程会加入就绪堆。
    • I/O 事件完成或定时任务到期时,相关任务会加入就绪堆。

调度堆(Scheduled Heap)

  • 作用:存储需要延迟执行的协程任务,通常用于定时任务。
  • 特点
    • 任务按时间戳排序,堆顶的任务是下一个即将到期的任务。
    • 事件循环会定期检查调度堆,将到期的任务移动到就绪堆。
  • 加入时机
    • 使用 asyncio.sleep() 或 loop.call_later() 等延迟操作时,协程会被添加到调度堆。

调度时机

协程被添加到调度堆或就绪堆的常见方式包括:

  1. **asyncio.sleep()**:
    • 当协程调用 await asyncio.sleep(delay) 时,事件循环会将协程封装为一个定时任务,并添加到调度堆中,等待 delay 时间后执行。
  2. **loop.call_later()**:
    • 通过 loop.call_later(delay, callback),事件循环会将回调函数封装为定时任务,并添加到调度堆中。
  3. **loop.call_at()**:
    • 通过 loop.call_at(when, callback),事件循环会将回调函数封装为定时任务,并添加到调度堆中,在指定时间 when 执行。
  4. **asyncio.create_task()**:
    • 使用 asyncio.create_task() 创建任务后,任务会在下一次 await 时自动添加到就绪堆。
  5. 协程嵌套调用
    • 当一个协程 A 中 await 另一个协程 B 时,协程 A 会通过 add_done_callback 方法注册一个唤醒回调。当协程 B 完成时,事件循环会唤醒协程 A 并将其加入就绪堆。

总结

asyncio 的调度机制基于事件循环和协程,通过就绪堆和调度堆管理任务的执行顺序。其核心特点包括:

  • 协作式调度:协程通过 await 主动交出控制权,事件循环根据任务状态调度执行。
  • 高效 I/O 处理:通过 select 系统调用监听 I/O 事件,避免阻塞。
  • 灵活的任务管理:支持定时任务、嵌套协程调用等多种调度方式。

理解 asyncio 的调度机制有助于编写高效、可维护的异步代码,充分利用 Python 的异步编程能力。


参考资料

作者

deepwzh

发布于

2025-03-01

更新于

2025-03-02

许可协议

评论