← 返回题目列表

asyncio 事件循环是什么?它在异步编程中负责什么?

高频 中等 第 4 / 27 题 更新于 2026/07/27
asyncio事件循环event loop

简化版

事件循环是 asyncio 的调度核心,负责运行协程、调度 Task、监听 I/O、处理定时器和回调。可以把它理解成一个不断循环的调度器:谁准备好了就运行谁,谁在等待 I/O 就先挂起。

详细版

常见入口:

import asyncio

async def main():
    await asyncio.sleep(1)

asyncio.run(main())

asyncio.run() 会创建事件循环,运行入口协程,结束后清理事件循环资源。

事件循环负责:

  • 驱动协程继续执行;
  • 管理 Task 的状态;
  • 处理 await asyncio.sleep() 这类定时器;
  • 监听 socket 等异步 I/O;
  • 执行回调;
  • 协调取消、异常、任务收尾。

面试中可以这样回答:事件循环是单线程异步并发的“调度中心”,协程本身不会自己跑,必须由事件循环驱动。

完整版教学

一、协程为什么需要事件循环

协程对象本身只是一个可暂停、可恢复的执行对象。它不会自动执行,也不会自己决定什么时候恢复。

事件循环负责做这件事:

检查哪些任务可以运行 → 运行到下一个 await → 记录等待条件 → 继续调度其他任务

所以 asyncio 程序通常需要一个入口:

asyncio.run(main())

没有事件循环,协程就像排队等开工的任务单。

二、事件循环如何实现并发

假设有三个请求任务:

async def fetch(name):
    await asyncio.sleep(1)
    return name

事件循环运行第一个任务,遇到 await asyncio.sleep(1),发现它要等 1 秒,于是挂起它,去运行第二个任务。第二个任务也等待,再去运行第三个。

1 秒后,这些任务陆续变成可运行状态,事件循环再恢复它们。

整个过程中不需要为每个任务创建一个线程。

三、asyncio.run 做了什么

日常代码推荐用:

asyncio.run(main())

它大致负责:

  1. 创建新的事件循环;
  2. 设置为当前运行环境的循环;
  3. 运行入口协程;
  4. 等待异步生成器和默认 executor 收尾;
  5. 关闭事件循环。

在普通脚本里,不建议自己手动反复创建和关闭 loop,容易出现资源清理问题。

四、事件循环不是多线程调度器

默认情况下,一个事件循环通常运行在一个线程里。同一时刻只有一个协程片段在执行。异步并发依赖的是协程在等待点让出执行权,而不是多个协程同时在多个 CPU 核上跑。

这意味着:

  • CPU 密集计算会卡住事件循环;
  • 阻塞函数会卡住事件循环;
  • 协程代码中长时间不 await,其他任务就没机会运行。
async def bad():
    while True:
        heavy_compute()

这种代码会让事件循环失去调度机会。

五、事件循环和 Web 框架的关系

FastAPI、aiohttp、Sanic 等异步 Web 框架底层都会依赖事件循环。请求进来后,框架把请求处理逻辑作为协程任务调度。

如果你的 handler 里使用异步数据库客户端、异步 HTTP 客户端,就能在等待 I/O 时释放事件循环去处理其他请求。

如果 handler 里直接使用阻塞数据库驱动或 requests.get(),事件循环就会被卡住,高并发优势会明显下降。

六、常见误区与追问

概念作用面试判断
协程对象表示一段可暂停的异步逻辑创建后不自动运行
Task把协程注册到事件循环才能被调度并发推进
事件循环轮询就绪任务和 I/O 事件单线程内协作式调度
asyncio.run创建、运行、关闭事件循环应作为异步程序入口使用
  • 误区:有 async def 就等于代码已经异步运行。 async def 调用后得到的是协程对象,只有被 await、包装成 Task 或交给事件循环才会执行。
  • 误区:事件循环就是多线程调度器。 默认事件循环通常在一个线程里运行,依靠协程主动在 await 处让出控制权实现并发。
  • 误区:asyncio.run 可以在任意地方反复嵌套调用。 它会创建并管理事件循环,不能在已经运行的事件循环中随意调用,Web 框架内部通常已经有循环。
  • 追问:事件循环为什么能处理很多连接? 网络 I/O 等待期间不占用当前协程执行权,循环可以切换去推进其他已就绪任务。
  • 追问:一个阻塞函数为什么会拖垮所有协程? 如果它长时间不 await 且占住事件循环线程,其他协程就没有机会被调度。
  • 追问:事件循环和 uvicorn、FastAPI 有什么关系? ASGI 服务器负责启动和管理事件循环,应用中的异步视图运行在这个循环之上。

记忆钩子:事件循环不是把 Python 变成多线程,而是在一个线程里安排“谁等 I/O,谁先继续跑”。

七、加强记忆

事件循环是 asyncio 的调度中枢:协程负责“能暂停”,Task 负责“可调度”,事件循环负责“什么时候继续跑”。它通常在单线程里通过 await 点切换任务,所以异步适合 I/O 并发,不适合把 CPU 重活直接塞进 loop。