Python 异步编程实战:asyncio 从入门到生产级应用

Python 的 asyncio 模块从 3.4 版本引入,到现在已经成为高并发场景的标准方案。但很多开发者对它的理解还停留在"加个 await"的层面。

一、为什么需要异步

传统的同步代码在遇到 I/O 操作时会阻塞线程。当你需要同时请求 100 个 API 时:

  • **同步方式**:串行执行,总耗时 = 100 × 单次耗时
  • **异步方式**:并发执行,总耗时 ≈ 单次耗时(受并发数限制)
import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as resp:
        return await resp.json()

async def main():
    urls = [f"https://api.example.com/item/{i}" for i in range(100)]
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
    return results

二、事件循环的本质

事件循环是 asyncio 的心脏。它做的事情很简单:

  1. 从任务队列取出一个协程
  2. 运行它,直到遇到 `await`
  3. 把控制权交给事件循环
  4. 检查是否有 I/O 完成
  5. 如果有,恢复对应的协程
# 等价的底层写法
loop = asyncio.new_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.close()

三、生产级最佳实践

3.1 控制并发数

无限制的并发会导致连接池耗尽或被目标服务限流:

semaphore = asyncio.Semaphore(10)

async def controlled_fetch(session, url):
    async with semaphore:
        return await fetch(session, url)

3.2 超时处理

try:
    result = await asyncio.wait_for(fetch(session, url), timeout=5.0)
except asyncio.TimeoutError:
    print(f"请求超时: {url}")

3.3 异常隔离

asyncio.gather 默认会在第一个异常时取消所有任务。生产环境建议用 return_exceptions=True

results = await asyncio.gather(*tasks, return_exceptions=True)
for i, result in enumerate(results):
    if isinstance(result, Exception):
        print(f"任务 {i} 失败: {result}")

3.4 生命周期管理

async def background_task():
    while True:
        await do_work()
        await asyncio.sleep(60)

async def app():
    task = asyncio.create_task(background_task())
    try:
        await run_server()
    finally:
        task.cancel()
        await task  # 等待清理完成

四、常见误区

  • **误区一**:异步一定比同步快。CPU 密集型任务用异步反而更慢,应该用 `multiprocessing`
  • **误区二**:可以在异步代码里用 `time.sleep()`。这会阻塞整个事件循环,必须用 `asyncio.sleep()`
  • **误区三**:所有库都有异步版本。很多库还是同步的,需要用 `run_in_executor` 包装

五、何时选择异步

| 场景 | 推荐方案 | |------|---------| | 高并发网络请求 | asyncio + aiohttp | | CPU 密集计算 | multiprocessing | | 混合场景 | asyncio + ProcessPoolExecutor | | 简单脚本 | 同步即可 |

异步不是银弹,但在 I/O 密集场景下,它能让你的程序性能提升一个数量级。