Python 最大冤案:你以为 `await` 在“死等”?它其实在“诈尸“
Python 最大冤案:你以为 await 在“死等”?它其实在“诈尸”!
别再对着 await 喊“阻塞”了,它比窦娥还冤。
大家好。
今天必须给 await 正个名。
很多人学 Python 异步,第一眼就跪了:
“既然都要写
await等着,我干嘛不直接写time.sleep?这不是脱裤子放屁吗?”
看起来,屏幕卡住了。
看起来,代码不动了。
看起来,和同步没区别。
停!这正是 99% 的 Python 初学者掉进去的深渊。
await 和同步的“等”,根本是两个物种。
一个是礼貌让行。
一个是躺平堵路。
一、别急着看代码,先去点杯奶茶
场景一:同步函数——你在柜台当“望夫石”
你去买奶茶。
店员说:“稍等两分钟。”
你双手插兜,眼珠子焊死在店员手上。
后面排队的西装大哥咳嗽提醒,你不动。
外卖小哥急得原地转圈,你不动。
你的世界里,只有那个还没封口的奶茶杯。
这就是 time.sleep(2)。
线程被你一个人锁死了。CPU 空转,啥也干不了,这叫:阻塞(Blocking)。
场景二:异步函数——你是时间管理大师
你又去买奶茶。
店员递给你一个震动取餐器(编号 #87)。
你拿过取餐器,转身就走。
你去隔壁买了煎饼果子,去菜鸟驿站取了快递,甚至还给手机贴了个膜。
“嗡嗡嗡” —— 取餐器震了。
你回去拿奶茶,顺便对后面的人说:“不好意思,刚才我排队了吗?我在隔壁忙着呢。”
这就是 await + create_task。
你把柜台位置让出来了。这叫:挂起(Suspending)。
二、await 的真实面目:它不是“等”,是“让”
很多人把 await 翻译成“等待”,这是翻译界最大的事故。
await 正确的潜台词是:
“报告总指挥(事件循环),我现在手里没活了(I/O阻塞),我先去旁边挂个号(挂起),您去招呼别人吧,我完事了会响铃的。”
一旦你说了这句话,当前函数就暂停了,控制权交还给事件循环。
记住这条铁律:
- 同步
time.sleep(1): 你睡着了,全世界都得等你睡醒。(堵路) - 异步
await asyncio.sleep(1): 你定了个闹钟,先去忙别的,闹钟响了再回来。(让路)
三、上证据:同一件事,效率差一倍
❌ 同步写法(串行堵路):
import time
def 买奶茶(名字):
print(f"{名字} 趴在柜台上死等...")
time.sleep(2) # 把柜台的过道堵死了
print(f"{名字} 终于拿到了")
买奶茶("张三")
买奶茶("李四")
# 耗时:4秒
# 感受:李四想把张三踢出店门。
✅ 异步写法(并发让路):
import asyncio
async def 买奶茶(名字):
print(f"{名字} 拿了个取餐器,先去逛街了")
await asyncio.sleep(2) # 把柜台让给下一位
print(f"{名字} 听到震动,回来取餐")
async def main():
# 重点在这里!!!
# 必须把任务“发射”到事件循环里,才叫并发
task1 = asyncio.create_task(买奶茶("王五"))
task2 = asyncio.create_task(买奶茶("赵六"))
# 这里的 await 是在等“两个取餐器都震完”
await task1
await task2
# 更优雅的写法:await asyncio.gather(task1, task2)
asyncio.run(main())
# 耗时:2秒
# 感受:王五和赵六谁也没碍着谁。
⚠️ 防坑指南:
千万别写成 await 买奶茶("王五") 接着 await 买奶茶("赵六")!
不 create_task,异步也白搭。
create_task 是发射按钮,await 只是接收器。
四、底层揭秘:那个“诈尸”恢复术是怎么做到的?
同步函数睡觉时,Python 解释器会把当前代码位置压栈(Push),然后线程就真的挂起了,操作系统都叫不醒它(除非中断)。
异步函数遇到 await 时,Python 玩了个心眼。
它把当前所有的局部变量、运行到第几行打包成一个快照(Generator Frame),存进内存角落。
然后对事件循环说:“哥,我挂机了,你拉别人吧。”
等网卡数据到了、时间到了,事件循环把这个快照从垃圾堆里捡出来,啪的一下贴在原来的位置上。
精确恢复,就像什么都没发生过一样,接着往下跑。
这就是协程的挂起-恢复机制。
它不是“一直等着”,它是“死了,但又没完全死”,随时准备诈尸。
五、终极拷问:我什么时候该用它?
-
如果你的代码是这样的: 处理一张大图、算一道数学题、本地文件压缩。
- 结论: 用同步。你用异步就是给自己找麻烦,因为计算密集型任务用异步不仅不加速,还会因为调度损耗变慢。
-
如果你的代码是这样的: 爬 100 个网页、调 50 个 API、连数据库发呆等返回、读硬盘里的碎文件。
- 结论: 必须用异步。
- 否则: 你就得开 100 个线程。在 Python 的 GIL 大锁面前,100 个线程就像 100 个胖子挤一个地铁闸机口,不仅不并发,CPU 反而在疯狂做上下文人浪,把自己累死也没干多少活。
写在最后
别再被 await 那副“我在等你”的无辜表情骗了。
它根本不是“死等”。
它是一个高情商的时间管理大师。
它在等待的时候,把舞台让给了全世界。
觉得有收获的话,点个「在看」。
也欢迎转发给那个还在 for 循环里 time.sleep 爬虫的朋友,让他看看自己电脑浪费了多少电。
P.S. 学会 await,你才算拿到了 Python 并发的驾照。
本文来自博客园,作者:Athenavi,转载请注明原文链接:https://www.cnblogs.com/Athenavi/p/19895977

浙公网安备 33010602011771号