最近用ChatGPT、Codex排查真实项目时,有一种问题特别容易让人产生错觉:

本地测试全绿,单线程跑也完全正常,一到真实并发场景,Bug又回来了。

比如:

单个请求修改库存,没有问题。

单个用户提交订单,没有问题。

单线程执行计数逻辑,也没有问题。

相关单元测试、接口测试甚至全部通过。

可一旦两个、十个甚至几十个请求同时进来,就开始出现:

库存被多扣。

订单重复创建。

状态互相覆盖。

计数结果少了一部分。

同一个任务被执行两次。

这时候很多人的第一反应是:

是不是Codex修得还不彻底?

但这种问题真正麻烦的地方在于:

代码可能在“一个请求一次执行”的情况下完全正确。

错误只会发生在:

两个或者多个执行路径同时碰到同一个状态时。

所以这里需要先分清一个非常重要的概念:

Sequential Correctness ≠ Concurrent Correctness

单线程正确,不代表并发正确。


一、先看一个最典型的例子

假设现在有一个库存:

stock = 1

用户购买时,代码逻辑很简单:

读取库存
如果 stock > 0
    stock = stock - 1
保存库存

单线程测试:

请求A进入。

读取库存:1。

判断通过。

减1。

保存0。

完全正确。

测试也一定通过。

但如果请求A和请求B几乎同时进来呢?

可能变成:

请求A读取:

stock = 1

请求B也读取:

stock = 1

A判断库存大于0。

B也判断库存大于0。

A减1,保存0。

B也按照自己刚才读到的1减1,再保存0。

最终数据库里:

stock = 0

看起来库存甚至没有变成负数。

但实际上:

两个用户都已经购买成功。

真正错误的不是减法。

而是:

两个请求都在基于同一个旧状态做决定。

这就是典型的:

Race Condition——竞态条件


二、为什么单元测试特别容易漏掉这种问题?

因为绝大多数普通测试,本质上都是:

一步一步执行。

比如:

准备数据。

调用函数。

检查结果。

整个过程中只有一个执行流。

测试看到的是:

A结束以后,B才开始。

但真实线上系统往往是:

A还没结束。

B已经进入。

C也在读取同一份数据。

所以测试环境验证的是:

顺序世界。

而线上运行的是:

并发世界。

如果代码逻辑依赖:

“我读取以后,数据不会被别人改”

这种隐含假设,

那么单线程测试再完整,也很难证明并发安全。


三、为什么AI修Bug时更容易忽略这一层?

因为Agent非常擅长沿着当前失败路径解决问题。

比如测试告诉它:

expected stock = 0
received stock = 1

它会分析:

哪一行更新失败。

哪个函数没有保存。

哪个条件判断不正确。

这些都属于:

局部执行逻辑。

但并发Bug真正关键的问题往往不是:

这一行写错。

而是:

这几行代码在两个执行流同时进入时,会发生什么。

如果测试本身没有模拟并发,

Agent也很容易默认:

当前操作是独占执行的。

于是代码在测试里会非常漂亮。

一上真实流量:

问题重新出现。


四、更深一层:很多并发Bug,本质上都是“检查”和“修改”之间存在时间窗口

比如:

if balance >= amount:
    balance -= amount

单看没有问题。

真正危险的地方是:

判断:

balance >= amount

和后面的:

balance -= amount

不是一个不可分割操作。

中间存在时间窗口。

第一个线程检查完以后,

第二个线程也可能进来。

这时候两个执行流都会觉得:

当前条件成立。

所以很多并发Bug真正需要解决的并不是:

“判断条件写得更严一点。”

而是:

如何保证“检查 + 修改”是一个原子过程?

这就是并发控制的核心。


五、最常见的第一类问题:Lost Update——更新丢失

比如两个请求同时修改计数器。

初始值:

count = 100

线程A读取100。

线程B也读取100。

A执行:

100 + 1 = 101

保存101。

B也执行:

100 + 1 = 101

再保存101。

理论上执行两次以后应该是:

102

最终却只有:

101

其中一次更新被覆盖了。

代码单独执行没有任何问题。

问题只存在于:

多个执行流共享同一个状态。


六、第二类问题:重复创建

例如:

用户点击一次付款。

前端因为网络慢又重试一次。

两个请求几乎同时进入。

后端逻辑都是:

查询订单是否存在。

不存在。

创建订单。

单线程完全正确。

并发情况下:

请求A查询:

不存在。

请求B查询:

也不存在。

A创建。

B也创建。

最终:

两个订单。

所以真正的问题是:

“先查有没有,再决定创建”并不是并发安全的。

这类问题往往需要:

唯一约束。

幂等Key。

事务。

原子写入。

而不是继续给查询逻辑加更多判断。


七、第三类问题:状态覆盖

比如一个任务状态:

pending

线程A准备更新成:

processing

线程B准备更新成:

cancelled

如果两边没有版本控制或者状态机约束:

谁最后写入,

谁就覆盖前面的结果。

于是你会看到:

明明任务已经取消,

过一会又变成processing。

或者:

明明已经完成,

状态却被旧请求重新改回处理中。

这类问题最容易出现于:

异步任务。

消息队列。

Webhook。

支付回调。

多个Agent同时修改共享状态。


八、为什么“加锁”也不是万能答案?

看到并发问题以后,很多人第一反应是:

那就加锁。

但锁本身也可能带来新的问题:

锁范围太大。

吞吐量下降。

锁顺序不一致。

死锁。

锁没有覆盖所有入口。

分布式环境下,本地锁根本无效。

所以真正需要先判断的是:

哪一段状态必须被保护?

而不是:

哪里报错就在哪里加锁。


九、数据库事务为什么也可能不够?

很多人觉得:

放进Transaction就安全了。

其实也不一定。

事务只能保证一部分一致性。

如果两个事务同时:

读到同一个值。

然后分别修改。

不同隔离级别下,仍然可能产生:

Lost Update。

幻读。

重复写入。

所以并发问题往往需要结合:

事务隔离级别。

行锁。

乐观锁。

版本字段。

唯一约束。

原子UPDATE。

一起考虑。


十、一个很实用的办法:把“读 → 判断 → 写”画出来

遇到这类Bug时,我不建议第一步直接让Codex修改代码。

先把执行过程画成:

Read
↓
Check
↓
Compute
↓
Write

然后问:

如果两个请求同时执行,

每一个步骤之间能不能被另一个请求插进来?

比如:

A Read

B Read

A Check

B Check

A Write

B Write

只要把执行顺序展开,很多并发Bug会非常明显。


十一、第二个办法:让Codex主动模拟Interleaving

可以直接要求Agent分析:

假设请求A和请求B同时执行,请列出可能的执行交错顺序,并判断哪些顺序会产生错误结果。

这个方法非常有效。

因为并发Bug最核心的东西就是:

Interleaving——执行交错

并不是代码逻辑本身错误。

而是不同线程、请求或任务之间的执行顺序不同以后:

结果改变了。


十二、第三个办法:测试必须真正制造并发

普通测试这样写:

run A
run B

并没有意义。

需要的是:

A ────────>
B ────────>

尽量同时进入关键区域。

例如:

同时启动多个请求。

使用Barrier让线程在同一个点一起继续。

重复运行数百次。

随机延迟。

提高问题出现概率。

因为很多竞态问题并不是每次都会发生。

它可能:

100次才出现1次。

如果只跑一遍:

测试很容易全部通过。


十三、为什么这类Bug经常“本地复现不了”?

因为并发Bug特别依赖时序。

你的本机可能:

CPU快。

负载低。

网络稳定。

请求恰好没有重叠。

线上却可能:

大量请求同时进入。

数据库响应变慢。

线程调度不同。

网络延迟变化。

于是原来只有几毫秒的竞态窗口,被放大了。

这就是为什么:

本地跑100次没问题,不代表线上一定没问题。

并发问题通常需要:

压力。

随机性。

重复执行。

才能真正验证。


十四、第四个办法:优先使用“原子约束”,不要只依赖代码判断

比如防止重复订单。

与其完全依赖:

if not exists:
    create

更可靠的方式可能是:

数据库唯一约束。

这样即使两个请求同时创建:

数据库也会阻止第二个。

又比如库存扣减:

可以使用:

带条件的原子UPDATE。

而不是:

先SELECT,再UPDATE。

很多时候:

最可靠的并发控制不是让代码更聪明,而是让底层系统保证不变量。


十五、第五个办法:明确哪些状态必须满足“不变量”

比如库存系统最核心的不变量:

stock >= 0

支付系统可能是:

同一个 payment_id 只能成功一次

任务系统可能是:

completed 状态不能重新回到 processing

如果这些Invariant能够写进:

数据库约束。

状态机。

测试。

Agent以后修改相关代码时,就更容易知道:

哪些东西绝对不能被破坏。


十六、为什么Agent越能自己执行,并发安全反而越重要?

因为未来不仅用户请求在并发。

Agent本身也会带来更多并发。

例如:

Agent A修改任务状态。

Agent B同时处理另一个相关任务。

后台Worker也在运行。

CI或者自动化任务又触发新的操作。

以前一个开发者一次只做一个任务。

现在系统可能同时存在:

多个自动执行主体。

这意味着:

并发不再只是“高流量网站”的问题。

多Agent Workflow本身也在增加:

状态竞争的机会。


十七、给自己测一个指标:并发失效率

这篇我建议只看一个指标:

Concurrent Failure Rate——并发失效率

选几个关键流程:

库存。

订单。

权限。

任务状态。

计数。

然后在并发条件下重复执行。

统计:

并发运行时出现状态错误、重复写入、更新丢失或结果不一致的次数 ÷ 总并发验证次数

比如:

对一个关键接口做100组并发测试。

其中出现4次:

重复创建。

状态错误。

数据不一致。

那么:

并发失效率 = 4%。

看起来4%不高。

但线上如果一天有10万次调用:

4%已经非常严重。


十八、并发失效率高于5%,说明什么?

如果核心流程一上并发就容易出现问题:

说明当前系统依赖了太多:

先读再写。

隐式顺序。

无保护共享状态。

这种情况下真正需要优化的是:

并发控制。

事务。

幂等。

原子操作。

状态机。

而不是继续堆更多AI任务。


十九、并发失效率1%—5%,最容易被误判为“偶发Bug”

这种最危险。

因为问题不是每次出现。

开发者很容易觉得:

再跑一次就好了。

但这种低概率竞态一旦进入高流量环境:

最终几乎一定会遇到。

所以这种阶段应该重点:

重复运行。

增加压力。

扩大时序随机性。

不要因为99次成功就忽略那1次错误。


二十、并发失效率长期接近0,才能说明流程真正稳定

如果关键操作在:

高并发。

重复执行。

随机延迟。

压力测试。

下都能保持:

状态一致。

没有重复副作用。

没有更新丢失。

说明并发控制已经比较成熟。

这个时候Agent进一步参与更多自动化任务,风险才更低。


二十一、Plus和Pro怎么判断?

如果你的并发失效率还比较高:

本地测试全部通过。

一上真实并发就出现:

重复写。

状态覆盖。

计数错误。

那当前真正的瓶颈并不是AI额度。

而是:

系统本身缺少并发安全保证。

这种阶段Plus通常已经足够。

更应该先让ChatGPT、Codex帮助你完善:

并发测试。

事务边界。

锁策略。

幂等Key。

唯一约束。

状态机。

因为增加更多Agent容量,甚至可能进一步提高并发操作数量,让问题暴露得更严重。


如果你的并发失效率已经长期接近0:

关键状态都有明确约束。

事务和幂等成熟。

高并发验证稳定。

Agent参与自动化也很少制造共享状态问题。

同时你又长期存在:

大型项目分析。

长任务。

多个稳定Agent任务排队。

AI侧容量真正限制开发吞吐。

这时候Pro才更容易带来真实价值。

因为增加的是:

建立在稳定并发基础上的AI产能。

而不是放大一个本来就存在竞态问题的系统。


二十二、并发Bug真正难的地方,是每一行代码都可能“看起来没错”

这是这类问题最值得注意的地方。

很多竞态问题,你单独看:

if判断没错。

计算没错。

数据库写入也没错。

每一步都合理。

只有把:

请求A。

请求B。

放在同一条时间线上,

错误才会出现。

所以以后让Codex处理这类问题,不能只问:

这一段代码对不对?

还要再问:

如果两个人同时执行这段代码,它还对不对?

这两个问题完全不是一回事。


最后

ChatGPT、Codex越来越能快速修复代码以后,测试全绿很容易给人一种感觉:

这个问题已经解决了。

但如果项目真正运行在:

多用户。

多线程。

多进程。

多个Worker。

多个Agent。

这样的环境里,

单线程正确只是第一步。

真正可靠的代码还必须回答:

多个执行主体同时进入时,系统还能不能保持同一个规则?

库存只能卖一次。

订单不能重复创建。

状态不能互相覆盖。

计数不能丢。

这些问题,普通单线程测试很难替你证明。

所以以后Codex修完一个涉及共享状态的Bug以后,我觉得很值得再问一句:

如果两个请求在完全相同的一毫秒进入,这段代码还成立吗?

如果这个答案还不确定,

那么测试虽然已经全绿,

真正的Bug可能只是:

还没有等到并发把它重新叫出来。

持续分享 Codex、大模型开发与 AI 编程实战内容。
长期深度使用各类代码大模型,也整理了
稳定的Plus/Pro会员订阅渠道,有需要可自取。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐