Python 异步并发,原来没你想得那么“随机”:DBOS 想把不可控变成可回放

开发工具 2026年4月4日
Python 异步并发,原来没你想得那么“随机”:DBOS 想把不可控变成可回放
DBOS 最近分享了一个颇有意思的发现:看起来充满不确定性的 Python async,其实在任务启动顺序上暗藏确定性。这件事不只是语言实现的小技巧,它直指一个更大的行业命题——当工作流、AI Agent 和云原生系统越来越依赖“失败后恢复”时,软件是否还能被可靠地重放,正变成新的基础能力。

当“并发”遇上“可恢复”,麻烦就来了

很多开发者对 Python 的 async/await 都有一种熟悉的敬畏:它很好用,写起来也优雅,尤其适合 I/O 密集型任务,比如批量调用 API、并发访问数据库、同时处理多个网络请求。但一旦系统从“跑得快”升级到“不能出错”,事情就没那么轻松了。

DBOS 这篇文章谈的,正是一个平时不太会被普通业务开发注意到的问题:如果一个异步工作流执行到一半宕机了,重启之后,系统怎么知道哪些步骤已经完成、哪些需要继续执行?这背后依赖的是一种叫“可持久化执行”或者“耐久工作流”的思路——把执行过程记录下来,出故障后再按原来的轨迹恢复。听起来很美,但前提很苛刻:流程必须是确定性的,也就是同样的输入,必须能重现同样的步骤顺序。

问题偏偏出在“并发”两个字上。开发者很喜欢用 asyncio.gather 一次性启动一批协程,因为这能把等待网络、磁盘、数据库的时间都叠起来,性能很好。可从恢复系统的视角看,这就像同时把几个球抛向空中:你知道它们都会落下来,但不确定谁先落地、谁中途被风吹了一下。对于需要“回放”的系统而言,这种不确定性是致命的。

Python 异步的秘密:它不是多线程,而是一条很规矩的单行道

DBOS 的有趣之处在于,它没有试图“消灭”并发,而是反过来研究 Python 的事件循环到底是怎么调度任务的。答案其实很有启发:大多数 Python async 程序看起来像很多事情在同时发生,但底层并不是多线程乱战,而是单线程事件循环在管理一个任务队列。

这意味着,协程并不会在你调用时立刻执行。你创建的是 coroutine,对象先被冻结在那儿;只有被 await,或者被 asyncio.create_task、asyncio.gather 这样的机制塞进事件循环里,它才会开始跑。而即便开始跑了,也不是大家真的一起向前冲——事件循环一次只执行一个任务,直到这个任务遇到 await、主动让出控制权,调度器才会切到下一个。

这里最关键的一点在于:新任务进入事件循环时,启动顺序是 FIFO,也就是先进先出。这个细节听起来像实现文档里的边角料,但 DBOS 抓住了它。假设你向 asyncio.gather 里按顺序传入三个协程,那么事件循环会先启动第一个,再启动第二个,再启动第三个。后续它们在各自 await 之后怎么交错,确实可能千变万化;但“起跑顺序”本身是稳定的。

这就很像一场马拉松。鸣枪之后,所有人最终会跑出不同节奏,途中还会补水、减速、超车,但至少离开起跑线的次序是安排好的。对于可回放系统而言,能确定“谁先起跑”,已经足够做很多事了。

DBOS 的做法聪明在哪:在第一次 await 之前,先把命运写进日志

DBOS 给出的工程解法颇有几分“在混乱中找锚点”的味道。既然异步任务在第一次 await 之前,会按照固定顺序被启动,那就把关键编号动作放在第一次 await 之前完成。

他们在 Python 的 @Step 装饰器里做了一件简单但重要的事:每个步骤在真正执行前,先从工作流上下文里领取一个递增的 step ID。由于这些任务的启动顺序是确定的,这些 ID 的分配顺序也就是确定的。于是,哪怕后面多个任务并发执行、完成先后不一,系统依然知道“这个任务逻辑上是第几步”。故障恢复时,就能根据这些编号和检查点,准确判断哪些步骤已完成、哪些要继续跑。

这类思路的迷人之处在于,它不靠重型锁、不靠把异步改回同步,也不靠要求开发者手写大量恢复逻辑,而是利用语言运行时已有的行为特征,做出一个足够稳定的顺序语义。从工程角度看,这是一种相当讨巧但有效的设计:不和运行时对抗,而是顺着它的脾气做系统。

不过这也提醒开发者,async 编程并不只是“语法糖”。很多人把 await 当成高级版函数调用,脑子里没有真正建立事件循环模型,于是代码一旦牵涉重试、幂等、恢复、事务边界,问题就开始冒出来。DBOS 这篇文章的价值,恰恰在于把那些平时被忽略的底层调度细节,变成了系统可靠性的基石。

为什么这件事在今天更重要:AI Agent、工作流平台都在抢“恢复能力”

如果把视野拉大一点,你会发现 DBOS 讨论的并不只是 Python 小众技巧,而是当前软件基础设施的一条新主线。

过去几年,Temporal、Prefect、Dagster、Airflow,甚至云厂商自己的工作流服务,都在强调一件事:现代应用越来越像“长事务系统”。一次业务处理可能要跨多个 API、多个数据库、多个外部服务,执行几十秒、几分钟,甚至更久。AI Agent 兴起之后,这种趋势更明显了。一个 Agent 可能要规划任务、调用工具、搜索资料、生成结果,中途任何一步失败,都希望它能从断点接着来,而不是全部重跑。

这时候,“可恢复”就不是锦上添花,而是基础设施的门槛。尤其在 Python 世界,这个问题格外现实。因为 Python 已经是数据工程、AI 应用、自动化脚本和后端编排的重要语言,大家都喜欢用 async 提升吞吐;可一旦要把这些流程做成生产级系统,就会撞上确定性和恢复这一堵墙。

Temporal 早年在工作流确定性上就非常强调约束,尤其在 Java、Go 等生态里,开发者需要小心时间、随机数、外部状态读取等一切可能导致重放不一致的因素。DBOS 现在试图在 Python async 的语境里,把这件事做得更自然一些。我个人的判断是,这会吸引一批对“工程可靠性”开始焦虑的 Python 团队,尤其是那些已经从写脚本走向构建长期运行系统的创业公司和平台团队。

真正的争议不在技术细节,而在边界:这种确定性到底能信到什么程度?

当然,事情也没到“Python async 从此完全可控”的程度。DBOS 利用的是任务启动顺序的确定性,但这并不等于整个异步执行过程天然就是确定性的。只要代码里混入了外部时间、随机数、非幂等 I/O、依赖外部服务返回顺序的逻辑,重放依然可能出问题。

换句话说,DBOS 发现的是一个极有价值的“确定性入口”,而不是拿到了万能钥匙。这一点很重要。因为工程领域最危险的时刻,往往不是你完全不知道,而是你“以为自己知道”。如果团队误把这种顺序保证理解成对全部运行结果的保证,后面还是会踩坑。

但这并不妨碍我认为它是个很漂亮的突破。它至少说明,Python async 并不像很多人想象得那样是一团彻底不可分析的迷雾。相反,它因为单线程事件循环和显式 await 的存在,反而比传统多线程更容易推导。线程世界里的竞态条件像厨房里同时飞舞的刀叉,async 世界更像一条轮流发言的会议流程——混乱仍然存在,但混乱有边界。

对开发者来说,这件事的启发甚至超出 DBOS 本身:如果你在设计异步框架、任务编排系统、Agent 运行时,别只盯着“并发性能”,还要问一句——出了故障以后,这套执行过程能不能讲清楚?能不能重放?能不能证明它和上一次是同一件事?

软件行业走到今天,大家已经不缺能跑起来的系统,缺的是出事之后还能体面站起来的系统。DBOS 这次对 Python async 的拆解,恰好击中了这个时代的痛点。它不炫技,甚至有点朴素,却很像那种真正能改变基础设施设计习惯的洞察。

如果说并发追求的是效率,那么确定性追求的其实是信任。接下来,谁能把这两件事同时做好,谁就更有机会成为下一代工作流平台和 AI 执行底座的赢家。

Summary: DBOS 这篇文章最有价值的地方,不是证明了 Python async 有多神奇,而是提醒行业:可靠性往往藏在运行时最不起眼的细节里。我的判断是,随着 AI Agent 和长时工作流普及,Python 生态会越来越重视“可回放执行”这类能力,类似 DBOS、Temporal 这类系统的思路也会继续下沉到更多框架中。未来的竞争,不只是让程序跑得更快,而是让它在失败之后还能按原样继续跑。
Python async/awaitDBOS异步并发asyncio.gather确定性执行可持久化执行耐久工作流故障恢复AI Agent云原生系统