Python协程调度模型_asyncio解析【教程】

Python协程调度核心是单线程内由asyncio事件循环驱动的协作式并发,依赖await显式挂起恢复、I/O自动让出控制权,需用aiohttp等异步库避免阻塞。

Python 的协程调度核心是 asyncio 事件循环(Event Loop),它不依赖操作系统线程,而是在单线程内通过“挂起-恢复”机制协同调度多个异步任务。理解其模型的关键在于:**事件循环驱动、协程对象需显式 await、I/O 操作自动让出控制权**。

事件循环是调度中枢

每个 asyncio 程序有且仅有一个运行中的事件循环(除非手动创建多个)。它持续轮询 I/O 事件(如 socket 可读、定时器到期)、执行就绪的回调和协程,是整个异步调度的“大脑”。

  • 调用 asyncio.run(main()) 会自动创建并启动一个事件循环
  • 手动管理时可用 loop = asyncio.get_event_loop()(Python 3.11+ 推荐 asyncio.get_running_loop()
  • 循环一旦关闭,无法重启;新任务必须在新循环中提交

协程函数与协程对象要分清

async def 定义的是协程函数,调用它返回的是一个协程对象(coroutine object),此时函数体并未执行——它只是个待调度的“任务描述”。必须将它交给事件循环执行(例如用 awaitasyncio.create_task())。

  • async def fetch(): return "done"fetch() 返回协程对象,不是结果
  • await fetch() 才真正执行,并返回 "done"
  • asyncio.create_task(fetch()) 把协程包装成 Task 并立即调度,适合并发启动

I/O 操作是让出控制权的触发点

asyncio 的高效依赖于“可等待对象”(awaitable)对 I/O 的封装。真正的系统级阻塞调用(如 time.sleep()requests.get())会卡住整个事件循环。必须使用 asyncio 原生支持的异步 I/O:

  • 网络请求用 aiohttp 替代 requests
  • 文件操作用 asyncio.to_thread()(Python 3.9+)或 loop.run_in_executor() 转移至线程池
  • 延时用 await asyncio.sleep(1),而非 time.sleep(1)
  • 所有底层基于 select / epoll / kqueue 或 Windows 的 IOCP 实现

Task 和 Future 是调度的基本单元

协程被调度后,通常会被包装为 Task 对象(继承自 Future),代表一个正在运行或已完成的异步操作。Task 可被取消、查询状态、添加回调。

  • task = asyncio.create_task(coroutine) 是推荐的并发启动方式
  • await task 等待其完成;task.cancel() 可中断执行(协程需响应 CancelledError
  • asyncio.gather(a, b, c) 并发运行多个协程并收集结果
  • asyncio.wait_for(task, timeout=2) 为单个任务设置超时

不复杂但容易忽略:asyncio 不是多线程,也不是多进程,它靠协作式并发在单线程里榨干 I/O 等待时间。写 async 函数只是第一步,关键在用对 await 点、选对异步库、管好事件循环生命周期。