深入理解 Python Asyncio:事件循环、Future 与协程调度本质

发布于 2026-03-10 · 分类:后端开发 · 阅读约 13 分钟
首页 / 后端开发 / 正文

Python 在 Web 领域长期受到 GIL(全局解释器锁)的限制。为了在单进程下榨干高并发 IO 性能,从 Python 3.4 的 asyncio 模块到 3.5 引入的 async/await 原生语法糖,Python 的异步并发能力进入了崭新的时代。

1. 协程的历史根源:生成器与 yield from

在底层,Python 协程之所以能够在函数执行中途“暂停”并交出 CPU 控制权,是基于经典迭代器协议中的 yield 操作:

2. 事件循环(Event Loop)与 Future/Task

事件循环本质是一个死循环,底层通过 Linux 的 epoll(或 macOS 的 kqueue)监听所有已注册的非阻塞 Socket 描述符:

import asyncio

async def fetch_api(url):
    print(f"Connecting to {url}...")
    await asyncio.sleep(1) # 非阻塞休眠,把控制权交还事件循环
    return f"Response from {url}"

async def main():
    # 并发调度两个任务
    task1 = asyncio.create_task(fetch_api("https://api.github.com"))
    task2 = asyncio.create_task(fetch_api("https://api.v2ex.com"))
    
    results = await asyncio.gather(task1, task2)
    print("All tasks finished:", results)

asyncio.run(main())

3. 避坑指南:阻塞调用切忌放入异步主线程

黄金法则:在 async 函数中,绝不能执行任何阻塞性调用(如 time.sleep()、传统的 requests.get() 或密集 CPU 计算)。 一旦阻塞,整个事件循环将彻底卡死,所有后续请求全部超时。

对于无法避免的 CPU 密集型任务或第三方同步库,应交由线程池执行:

loop = asyncio.get_running_loop()
# 将同步阻塞操作转交给默认 ThreadPoolExecutor 运行
result = await loop.run_in_executor(None, sync_heavy_task, arg1)
本文作者:Alex Chen
本文链接:/posts/python-asyncio-internals/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。