Python兄弟吐槽:从 async/await 到虚拟线程

banq


别再整花里胡哨的 async 了!咱要回炉重造,搞“虚拟小弟”并发!——一个被 async/await 折磨疯了的程序员的呐喊

作者: 一个在去年11月就说“线程才是亲儿子”的倔强程序员,今年7月又来补刀了。

第一章:当年咱们为啥搞出 async/await 这个“怪胎”?

话说啊,咱们 Python 社区以前搞并发(就是让电脑同时干好几件事),那叫一个乱。

以前是用“线程”——你可以想象成你雇了一堆小弟帮你跑腿。但问题是,这些小弟太能吃(占用内存),而且动不动就打架(死锁、数据冲突),老板(程序员)管都管不过来,累得半死。

于是,天才们想了个“妙招”:async/await

这玩意儿说白了就是:“你先别动!等我说‘await’,你再动!”

听起来很酷对吧?但它带来了两个“祖传 bug”:

1.  “颜色污染”:你的函数突然分成了“蓝色函数”(async 函数)和“红色函数”(普通函数)。蓝色函数只能调蓝色函数,红色只能调红色。整个代码库就像被泼了颜料,红蓝大战,混乱不堪!想混用?门儿都没有!程序员天天头疼:“我这函数该涂什么色?”

2.  “内部机关太复杂”:async/await 底下藏着一个超级复杂的“调度器”,像个隐形的傀儡师,控制着所有小弟的行动。但这个机关时不时就“漏油”,把复杂的内部问题暴露给程序员,搞得大家一头雾水。

好处也不是没有:它让更多人开始接触并发编程了,就像给全班同学发了本《如何当老板》的入门手册。但问题是,这手册教的是“用意念遥控小弟”,太玄乎了!

第二章:线程:被误解的“真·亲儿子”

作者说:“醒醒啊兄弟们!咱们一开始想解决的问题是‘小弟太能吃’,结果我们没去给小弟减肥,反而发明了一套‘意念遥控术’,结果还把大家脑子搞坏了!”

线程,其实是个好东西!概念简单粗暴:“你,去干那事!” 就完事了。

但它的问题在于,以前的“线程API”(就是雇小弟的合同模板)写得太烂了,各种坑,搞得大家一提线程就摇头。

但是! 现在情况变了!Python 准备上“自由线程”(free-threading)了!这意味着“意念遥控术”(async/await)最大的优势——“我执行到 await 之前绝对安全”——突然就没用了!

为啥?因为即使你不用 async,其他线程也可能随时来抢你的活儿干!你写的代码还是得考虑“并发安全”!

结果就是: 我们现在要同时伺候两尊大神——又得搞 async/await 的那一套花活,又得防着传统线程的各种坑。程序员的精神状态,危!

第三章:新希望!“结构化并发”和“虚拟小弟”来了!

就在大家快被逼疯的时候,async 社区搞出了一个“真·良心发明”:结构化并发

这玩意儿的核心思想就一条:“子小弟不能比老板活得久!”

想象一下,你(老板)派几个小弟(子任务)去办事。如果其中一个小弟搞砸了(抛出异常),你作为老板立刻下令:“全体撤退!任务取消!” 其他小弟也得马上停手。

这多合理啊!以前的线程就像野孩子,派出去就不管了,可能永远不回来,或者死在外面(内存泄漏)。

Python 的

task groups
就是这个思想的实现。但它有个致命伤:“取消”太难了!

举个栗子:有个叫

aiofiles
的库,它为了让读写文件也能用 async,偷偷摸摸开了个“真实小弟”(线程池)去干活。问题来了:你喊“取消”!那个在真实小弟身上的读文件操作,它根本听不见! 它还在那儿傻等文件读完。

结果就是:你想让所有小弟收工,但有一个小弟在“睡大觉”(阻塞I/O),整个团队就被卡死了!你只能按

Ctrl+C
(键盘中断)去“物理超度”它。这体验,就像你想解散班级,但有个同学在厕所里不出来,你只能踹门。

作者怒吼: 这不行!太糟糕了!我们必须回到起点,问自己:“如果从一开始,我们就只用线程,但给它一个超级牛逼的API,世界会怎样?”

第四章:乌托邦蓝图——“虚拟小弟 + 结构化老板”

作者画了个大饼,名字叫:“虚拟线程” + “结构化线程组”

虚拟线程(Virtual Threads):想象你有成千上万个“影分身小弟”。它们看起来和真实小弟一样能干活,但特别轻量,不怎么吃内存。系统(忍者大师)会自动把它们安排到少数几个真实小弟(内核线程)身上轮班干活。完美解决了“小弟太能吃”的问题!

结构化线程组(ThreadGroup):这就是那个“明事理的老板”。他派小弟出去,心里门儿清谁是他派的,谁干啥。一旦出事,一声令下,全员召回!

来看看代码怎么写(作者YY版):

python
普通写法:一个一个下,前面的失败了,后面的就别想下了。
def 下载所有网址(网址列表):
    结果 = {}
    for 网址 in 网址列表:
        结果[网址] = 下载(网址)  卡在这,等它下完
    return 结果

未来写法(美滋滋版):
def 下载所有网址(网址列表):
    结果 = {}
    老板(ThreadGroup)拍桌子:"开组会!"
    with ThreadGroup() as 老板:
        for 网址 in 网址列表:
            老板大手一挥:"你!去下这个网址!"
            老板.spawn(下载, 网址, 结果)  spawn = 派小弟
    组会结束,所有小弟必须归队!
    return 结果

这代码牛在哪?

1.  简单! 没有

async
await
Future
这些鬼东西!程序员只需要懂“老板派小弟”。
2.  安全! 任何一个小弟失败,老板立刻叫停,其他小弟收到“取消”信号,该收工收工。
3.  自动! 那个
结果
字典,系统会自动加“锁”(想象成保险柜),防止多个小弟同时往里塞东西打架。
下载
函数如果卡住了(比如等网络),虚拟线程会自动“挂起”,让出位置给其他小弟干活,不浪费资源。
4.  可控制! 老板还能规定:“一次最多派8个小弟!” (
max_concurrency=8
),防止把网络或服务器搞崩。


第五章:现实妥协与未来展望

作者也知道自己在画饼。这种完美的语法在 Python 里可能加不进去(Python 的语法太死板)。

所以他退一步说:“就算不能这么完美,我们也要朝这个方向努力!”

  •   未来可能的写法:还是得写点辅助函数,但核心思想不变——用
    with ThreadGroup
    来管理生命期。
  •   Futures 还在吗? 还在!但它是“高级玩法”。就像你可以直接问老板:“那个叫小张的小弟,他干完活了吗?结果拿给我!” 这通过
    spawn
    返回的“小票”(Future)就能查。
  •   能不能偷偷派小弟? 作者觉得不行!Trio 库的做法很对:没有老板(nursery),你就不能派小弟! 否则又会回到“野孩子满天飞”的老路。可以搞个“默认老板”处理后台任务,但必须在程序退出时强制收工。

最后,作者灵魂发问:

> “async/await 的未来在哪?”

他的回答很绝:“老代码就让它留着吧,当个古董。但新代码?拜拜了您嘞!咱们要拥抱‘纯线程’的新世界!”

因为,谁愿意天天纠结函数该涂什么颜色呢?我们要的是——‘写代码,派小弟,拿结果’,就这么简单!

总结成一句话:  
async/await 把简单问题复杂化,还搞出“颜色污染”。现在是时候抛弃它,用“虚拟小弟”(虚拟线程)+ “明事理的老板”(结构化并发)来回归简单、强大、统一的并发编程了!