人人都会AI编程

16.4 协程与异步 IO

更新时间:2026-07-12

当你需要同时处理成百上千个网络请求、WebSocket 连接或文件读写时,传统的多线程模型可能因为线程切换开销和 GIL 限制而力不从心。协程与异步 IO 正是 Python 在 IO 密集型场景下的高性能解决方案。

协程是什么

协程(Coroutine)是一种可以在执行过程中暂停、稍后再恢复运行的函数。它与线程不同:

  • 线程由操作系统调度,切换时消耗资源较大,且有 GIL 限制。
  • 协程由程序自身在“等待”时主动让出控制权,切换开销极小,不存在锁竞争问题。

在 Python 中,使用 async def 定义的函数就是一个协程函数,调用它不会立即执行,而是返回一个协程对象。

异步 IO 能解决什么问题

假设你需要下载 100 个网页。同步写法是一个一个下载,总耗时等于每次下载时间的累加——大部分时间花在等待网络响应上,CPU 空闲。异步则可以:

  • 发起第 1 个请求后,不等响应,立即发起第 2 个、第 3 个……直到全部请求发出。
  • 然后,异步循环会“监听”这 100 个连接,哪个先返回数据就处理哪个,继续等待下一个。

整个过程单线程完成,却实现了类似多线程的并发效果,而且避免了线程切换和锁的开销。这就是协程与异步 IO 的价值:让 IO 密集型操作的并发变得轻量而高效

asyncio 核心概念

Python 标准库的 asyncio 提供了实现异步编程的完整基础设施。有几个核心角色:

  • 事件循环(Event Loop):大管家,负责调度和执行所有异步任务。它不断轮询注册到它身上的协程和 IO 事件,发现谁就绪了就执行它。
  • 协程(Coroutine):由 async def 定义的函数。通过 await 关键字可以挂起自己,等待另一个协程或 IO 操作完成,让出控制权给事件循环去执行别的事。
  • 任务(Task):将协程包装成一个任务交给事件循环调度,可以并发运行多个任务。

实际编码时,你通常只需要关心 asyncawait 两个关键字,至于事件循环如何驱动,由 asyncio.run() 在幕后完成。

一个典型的异步请求示例

import asyncio
import aiohttp  # 异步 HTTP 客户端

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = [
        "https://example.com",
        "https://python.org",
        "https://github.com",
    ]
    # 并发执行多个 fetch 协程
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)
    for url, content in zip(urls, results):
        print(f"{url}  downloaded {len(content)} bytes")

# Python 3.7+ 启动事件循环
asyncio.run(main())

代码解读:

  • fetch 协程中用 await 挂起等待网络响应,期间事件循环可以运行其他协程。
  • asyncio.gather(*tasks) 将多个协程任务打包并发执行,等全部完成后返回结果列表。
  • asyncio.run(main()) 创建事件循环并运行主协程,直到它结束。

异步 IO 的优势与适用边界

优势

  • 极高的 IO 并发效率:单线程即可管理数万长连接(如实时聊天、游戏服务器推送、API 网关)。
  • 内存与调度开销小:协程切换只是函数栈帧的切换,远轻于线程。
  • 代码结构清晰:async/await 语义让异步代码看起来像同步代码,避免了回调地狱。

但并非万能

  • CPU 密集型任务(如矩阵运算、图像处理)放到协程里会阻塞事件循环,需要用多进程或线程池把计算任务抛出去异步执行(loop.run_in_executor)。
  • 已有的同步库(比如同步版 requests)不能在协程里直接 await,需要使用对应的异步库(如 aiohttpaiomysql),或用线程池包装。
  • 调试复杂度比同步代码高,栈追踪信息可能不够直观,需要理解事件循环的调度顺序。

与多线程 / 多进程的对比选型

  • 多线程(threading):适合 IO 密集型但并发量不是极端大(例如几十个线程),但受 GIL 影响,CPU 密集型不会因多线程加速,且线程切换有一定开销。
  • 多进程(multiprocessing):适合 CPU 密集型,可充分利用多核,但进程间通信开销大,资源占用多。
  • 协程(asyncio):最适合高并发 IO 密集型场景(网络服务、爬虫、数据库查询等),在单机就能支撑极高连接数,是目前编写高性能 Python 网络服务的标准范式。

一句话总结:当你需要同时处理大量等待操作且不想被线程管理、锁和 GIL 困扰时,协程 + 异步 IO 是 Python 生态中最优雅高效的解决方案。