Python 异步编程的几个坑
示例文章,用于演示分页与列表排版,请替换成你自己的内容。
异步代码看起来像是把 def 改成 async def、把调用加上 await 就完事了,但真正上线之后出问题的地方往往不在语法上。
阻塞调用会把整个事件循环卡住
协程之所以能并发,是因为遇到 IO 等待时会把控制权交还事件循环。如果协程里调用了阻塞函数——比如 requests.get、time.sleep、或者一个没做异步封装的数据库驱动——事件循环就停在那一行,所有并发任务一起等待。
症状很有迷惑性:单请求延迟正常,并发一上来延迟就线性增长,看起来像是下游服务慢。要定位它,可以在压测时检查事件循环的滞后。
异步不是线程
两者的适用场景不同:
| 异步 | 多线程 | |
|---|---|---|
| 适合 | IO 密集、连接数多 | 阻塞库无法改写 |
| 切换开销 | 小 | 大 |
| 共享状态 | 单线程,仍要注意 await 处的交错 | 需要锁 |
CPU 密集的任务两者都不合适,那要用多进程。
def hello(name):
a = 1
b = 2
c = a + b
print(f"{name}", c)
return c
别在 await 之间假设状态没变
单线程不代表没有竞态。任何 await 都是一个潜在的交错点,前后读到的共享状态可能已经被别的任务改过。需要原子性的读改写操作,要么用锁,要么改成不含 await 的临界区。