Skip to content

为什么需要 asyncio

先看一个普通同步程序:

python
import time

def fetch(name, delay):
    print(f"{name}: send request")
    time.sleep(delay)
    print(f"{name}: response")

fetch("profile", 0.4)
fetch("orders", 0.2)
fetch("recommendations", 0.3)

这段代码的慢点在等待。第一个请求没回来,第二个请求就不能开始;第二个没回来,第三个也只能排队。线程大部分时间没在做计算,只是在等结果。

asyncio 适合这种场景:手上有很多 I/O 工作,比如请求接口、读写 socket、等子进程输出。每件事本身算得不多,主要时间都花在等外部响应上。我们想在一个线程里把这些等待时间错开,而不是等完一个再做下一个。

并发不是并行

概念重点在 asyncio 里的含义
并发多件事在一段时间内交替往前走一个 Task 等 I/O 时,事件循环去跑另一个 Task
并行多件事在同一时刻真的同时运行需要多线程、多进程或多机器
阻塞当前执行流等到结果回来前不让出控制权time.sleep()、同步 socket、同步数据库调用
非阻塞操作暂时没结果时先返回,稍后再通知selector 监听 socket 就绪,Future 表示未来结果

asyncio 默认不会让 Python 代码同时跑在多个 CPU 核上。它做的是另一件事:一个任务在等网络时,别让线程闲着,先去处理别的任务。

一个请求聚合场景

假设你要构造用户首页,需要同时拿用户资料、订单、推荐结果:

同步写法总耗时接近 0.4 + 0.2 + 0.3 = 0.9s。如果三个请求彼此独立,理想耗时接近最慢的 0.4s

asyncio 要做的就是这件事:把这些等待时间重叠起来。三个请求一起发出去,谁先回来先处理谁,最后再把结果汇总。

asyncio 适合什么

适合:

  • 高并发网络客户端,例如批量调用 HTTP API。
  • 网络服务,例如 TCP 服务、WebSocket 服务和异步 Web 框架内部。
  • 需要排队、限流、超时、取消的 I/O 工作流。
  • 子进程、管道、socket 等可以由事件循环管理的 I/O。

它不适合直接处理:

  • 大量 CPU 密集计算。可以配合 asyncio.to_thread()run_in_executor()、多进程或专门的计算框架。
  • 已经被同步库锁死的调用链。同步数据库驱动放进 async def 里不会自动变成异步。
  • 需要系统强行打断任务的场景。asyncio 里的协程只有走到 await,才会把执行机会让出来。

为什么它适合 I/O 程序

asyncio 的调度方式比较克制。它不会在你代码写到一半时突然停下来,只有遇到 await 才会暂停当前协程。比如:

python
async def handle():
    state["step"] = "start"
    await fetch_remote_data()
    state["step"] = "done"

读这段代码时,你可以把注意力放在 await 上。await 前面的赋值会连续执行完;到了 await fetch_remote_data(),当前协程可能暂停,事件循环会去跑别的任务;等远端数据回来,再从下一行继续。

这个设计让暂停位置很清楚:主要看 await 在哪里。但它也有代价。如果你在协程里写了很长的 CPU 循环,中间一直没有 await,事件循环就没机会处理别的任务:

python
async def bad():
    total = 0
    for i in range(100_000_000):
        total += i
    return total

这类代码要么拆小并主动让出,要么放到线程池、进程池或专门的计算框架里。asyncio 更适合等待很多、计算不重的程序。

小练习

运行:

bash
python3 examples/asyncio_demos/02_concurrent_sleep.py

观察输出顺序和总耗时。然后把 asyncio.gather(...) 改成三个连续的 await fetch(...),比较总耗时。

面向学习目的的 Python asyncio 中文教程与 mini_asyncio 教学运行时。