30 分钟
Python 高级

asyncio 事件循环深度

理解事件循环原理、Task 调度和异步陷阱

  • 理解事件循环的工作原理
  • 掌握 Task/Future 的状态机
  • 理解 async for/async with 协议
  • 识别并避免异步陷阱

asyncio 事件循环深度

asyncio 是 Python 异步编程的核心。理解事件循环原理,才能写出正确的高性能异步代码。

事件循环原理

事件循环是个无限循环,不停从就绪队列里取回调执行。核心就是等I/O的时候,切去别的任务跑。

示例代码(可运行)

Task 与 Future

示例代码(可运行)

异步陷阱:阻塞调用

示例代码(可运行)
🚫异步代码最大的坑

在 async 函数中调用任何同步阻塞操作(time.sleep、requests.get、open() 大文件、CPU 密集计算)都会冻结整个事件循环,所有并发都失效。解决方案:用异步替代(aiohttp 替代 requests、asyncio.sleep 替代 time.sleep)用 loop.run_in_executor 把阻塞调用丢到线程池CPU 密集用 ProcessPoolExecutor。

async for / async with

示例代码(可运行)

并发控制:信号量与超时

示例代码(可运行)
🐍资深工程师经验谈

高并发 LLM 应用的架构:用 Semaphore 控制对 LLM API 的并发(避免触发限流)用 wait_for 设置超时(防止挂死)用 gather 的 return_exceptions=True 防止一个失败拖垮全部用 TaskGroup(Python 3.11+)自动管理任务生命周期流式输出用 async generator + SSE背压:用有界队列 Queue(maxsize=N) 防止生产过快消费不过来。

调度原理

事件循环的调度机制

asyncio 事件循环的核心调度逻辑:

Python
# 简化的事件循环伪代码
class SimpleEventLoop:
    def __init__(self):
        self._ready = deque()    # 就绪回调队列
        self._scheduled = []     # 定时任务(堆)

    def run_forever(self):
        while True:
            # 1. 把到期的定时任务加入就绪队列
            # 2. 执行就绪队列中的所有回调
            # 3. 如果没有就绪任务,用 selector 等待 I/O
            #    (epoll/kqueue/IOCP)
            # 4. I/O 就绪后把对应的回调加入就绪队列

# selectors 模块封装了:
# - Linux: epoll
# - macOS: kqueue
# - Windows: IOCP
# 这就是为什么 asyncio 能高效处理上万并发
# 它不是轮询,而是操作系统级的 I/O 多路复用
选择题

在 async 函数中调用 time.sleep(1) 会怎样?

选择题

限制并发请求数量应该用什么?

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

@decorator 写在函数定义上方,它在「模块加载、函数被定义时」就执行一次,把原函数包装成新函数;之后每次调用,走的都是包装后的版本。带参数的装饰器本质是三层嵌套(外层收参数、中层收函数、内层收调用参数)。务必用 functools.wraps 保留原函数名和文档,否则调试栈和日志里全是 wrapper。

挑战任务

并发下载器

简单+50 XP

用 asyncio + Semaphore 实现一个并发限流下载器,最多 3 个并发。

并发下载器
2 个测试用例

课后作业

带超时的并发

中等+30 XP

用 asyncio.gather + wait_for 实现一组任务,每个任务最多等 1 秒。

带超时的并发
1 个测试用例