Apollo 技术文档Apollo 技术文档
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
  • MMORPG 架构 QA

Q78: 如何处理竞态条件?

核心结论

竞态条件的本质不是“线程多了就会乱”,而是多个执行路径对同一份状态的读写顺序不再可控。

最有效的处理方式通常不是事后补锁,而是先减少共享状态、明确状态所有权,并让关键修改具备原子边界。

一、竞态条件真正是什么

只要程序结果依赖于执行先后顺序,而这个顺序又不受控制,就可能出现竞态。

它不只发生在:

  • 多线程

也可能发生在:

  • 异步回调
  • 定时器与主流程交错
  • 跨服务重复请求

所以竞态是“状态时序问题”,不只是“线程问题”。

二、最常见的竞态来源

例如:

  • 检查后再修改,中间状态已变化
  • 同一对象被多个线程同时写
  • 超时重试和原请求并发到达
  • 回调结果比对象生命周期更晚返回

这些比课本上的 counter++ 更接近真实线上问题。

三、最有效的第一步通常是减少共享

如果一份状态可以明确归属于:

  • 某个线程
  • 某个 Actor
  • 某个会话或房间

那很多竞态就会自然消失。

这通常比在共享对象上不断补锁更稳。

四、原子边界必须明确

很多竞态来自:

  • 读出旧值
  • 计算新值
  • 写回时已经过期

所以关键更新通常需要:

  • 原子操作
  • 锁保护
  • 版本号
  • compare-and-swap

具体选什么,取决于共享模式和复杂度。

五、检查与使用之间最容易出问题

这是实际工程里非常典型的一类竞态:

  • 先判断“对象存在”
  • 下一行使用时对象已经被销毁或转移

所以很多时候真正需要的是:

  • 生命周期绑定
  • 版本校验
  • 持有期内的稳定引用

而不只是再加一把锁。

六、异步系统里的竞态往往更隐蔽

例如:

  • RPC 超时后又晚回
  • 定时器取消后回调仍可能到达
  • 玩家下线后数据库异步保存才回来

这类问题常见处理方式包括:

  • 请求 ID
  • 版本号
  • 状态机校验
  • 失效标记

七、工程上更稳妥的处理方式

常见做法是:

  • 状态尽量单拥有者
  • 多步修改压成原子边界
  • 异步回调带上下文 ID 或版本
  • 关键对象生命周期显式管理

这样通常比“哪里出问题就哪里补锁”更稳。

八、常见误区

1. 竞态条件就是数据竞争

数据竞争只是其中一种。很多业务竞态发生在异步链路和状态机之间。

2. 加锁就能解决所有竞态

不一定。生命周期、重复请求和异步回调问题,很多时候需要状态机和版本控制。

3. 只有并发高才会有竞态

低并发下也可能发生,只是更难复现。

参考资料

  • 竞态条件、版本控制与异步状态机实践资料
在 GitHub 上编辑此页
最后更新: 3/20/26, 6:06 AM
贡献者: cuihairu