python事件循环是asyncio核心,通过协作式调度实现单线程高效i/o处理:await让出控制权,事件循环按就绪队列和定时堆调度任务,底层依赖epoll/kqueue/iocp,不轮询、不阻塞、不并行。

Python 的事件循环(Event Loop)是 asyncio 的核心,它负责调度和执行异步任务,让单线程能高效处理大量 I/O 密集型操作。它不靠多线程抢占,而是通过协作式调度:任务主动让出控制权(比如等待网络响应时 await),事件循环再唤醒其他就绪任务。
事件循环如何启动与运行
调用 asyncio.run(main()) 会自动创建并启动一个事件循环。它本质上是一个持续运行的 while 循环,每次迭代做三件事:检查哪些任务已就绪(如 I/O 完成、超时触发)、执行它们的回调或协程片段、然后休眠直到下一个事件发生(通常由操作系统通知,如 socket 可读)。
- 底层依赖系统级 I/O 多路复用(如 Linux 的 epoll、macOS 的 kqueue、Windows 的 IOCP),不是轮询
- 每次循环迭代称为一个“tick”,一个 tick 内可能执行多个回调,但不会中断正在运行的协程
- 你也可以手动获取并运行循环:
loop = asyncio.get_event_loop(); loop.run_until_complete(coro)
协程如何被调度和暂停
当你 await 一个 awaitable(如 asyncio.sleep() 或 loop.sock_recv()),当前协程会挂起,并把控制权交还给事件循环。此时协程状态被保存(在生成器帧中),同时向事件循环注册一个“恢复条件”(例如“500ms 后唤醒”或“这个 socket 有数据可读时唤醒”)。
- await 并不等于“立刻切换”,而是告诉事件循环:“我暂时不能继续,等 XX 条件满足再叫我”
- 事件循环维护一个“就绪队列”(ready queue)和一个“延迟任务堆”(heap for timeouts),定时检查并把到期任务推入就绪队列
- 所有协程都运行在同一个线程内,没有线程切换开销,但也不能利用多核 CPU(需配合
ProcessPoolExecutor)
回调、任务与 Future 的角色分工
事件循环调度的基本单位是 Callback 和 Task。普通回调(如 loop.call_soon())是一次性函数;Task 是对协程的封装,具备状态管理(pending/running/done)和取消能力;Future 则是结果容器,支持 set_result() 和 add_done_callback(),常被 Task 内部使用。
立即学习“Python免费学习笔记(深入)”;
-
asyncio.create_task(coro)立即把协程包装为 Task 并加入调度队列,实现“后台并发” - await 一个 Future 实际是在等待它被 set_result/set_exception —— 这个动作通常由 I/O 完成回调触发
- 网络库(如 aiohttp)内部就是用 socket 回调 + Future + Task 构建出高层的 async/await 接口
常见误区与关键细节
很多人误以为 await 会“开启新线程”或“自动并行”,其实它只是让出控制权;也有人以为事件循环会“自动优化执行顺序”,但它只按就绪顺序和优先级(如 call_soon() 比 call_later() 更早)调度,不保证公平性或最短响应时间。
- 阻塞调用(如
time.sleep()、普通requests.get())会让整个事件循环卡住——必须用asyncio.sleep()或loop.run_in_executor() - 未被 await 的协程对象只是生成器,不会运行;忘记
await是常见 bug,会看到<coroutine object ...></coroutine>警告 - 一个 Python 进程同一时间只能有一个运行中的事件循环(主线程默认有,子线程需手动创建)










