并发模型总览:从七周七并发到游戏运行时
Category
server / runtime 子树。节点定位:把并发立场先摆全(共享内存、不可变、身份状态分离、Actor、CSP、数据并行、数据分层)再判断——游戏工程不能只照书抄。runtime 子树入口。
Definition
如果第 5 章只讲“单线程、多线程、Actor、协程”几个词,范围其实是不完整的。因为并发问题从来不只有一种拆法,它背后至少有一整套不同立场:共享内存并发、不可变数据与纯函数、身份与状态分离、Actor、CSP、数据并行,以及把实时层和批处理层拆开的数据分层思路。
《Seven Concurrency Models in Seven Weeks》这类材料真正值钱的地方,不是让你照抄一套“圣经架构”,而是给你一张并发模型坐标图。游戏工程需要的,不是机械照搬,而是知道哪些模型适合做游戏主运行时,哪些只适合做局部工具,哪些更适合外围数据系统。
Problem
只讲「单线程、多线程、Actor、协程」几个词范围不完整:并发问题背后是一整套立场之争。立场不摆全,选型会退化成信仰之争——各自熟悉的立场各说各话,谁职级高听谁的。
先把常见并发立场摆出来
如果按那条经典脉络来拆,最常见的七类模型可以概括成:
- 线程与锁。
- 函数式编程。
- 身份与状态分离,例如 STM / Refs / Agents 这一类思路。
- Actor。
- CSP,也就是 Communicating Sequential Processes。
- 数据并行。
- Lambda 架构。
这七类模型并不是互斥技术栈,而是七种不同的并发立场。它们关注的重点完全不同:
- 有的在解决共享内存怎么管。
- 有的在解决状态怎样不被随手改。
- 有的在解决流程如何由多个顺序单元协同。
- 有的在解决大批量计算怎样并行。
- 有的在解决在线与离线怎样分层。
游戏工程为什么不能只照书抄
游戏不是纯后端业务,也不是纯科学计算,更不是纯离线数据平台。它通常要同时处理:
- 强实时链路。
- 单写权威状态。
- 高频对象更新。
- 弱网、断线与重连。
- 长线资产和外围服务。
所以游戏运行时经常会长成混合体:
- 战斗主链路像单线程、Actor 或 Tick 驱动状态机。
- 引擎热路径像数据并行和 Job System。
- 会话和接入更像协程、状态机和 Channel。
- BI、审计、推荐更像事件流和数据分层架构。
也就是说,游戏项目真正做的,通常不是“选择一个模型”,而是把不同模型放到不同位置。
一张更有用的总览表
| 模型 | 核心思想 | 最强价值 | 主要代价 | 在游戏里更适合什么 |
|---|---|---|---|---|
| 线程与锁 | 多线程共享内存,加锁保护 | 直接、通用、兼容遗留系统 | 争锁、抖动、时序难推理 | 工具链、后台任务、部分服务基础设施 |
| 函数式编程 | 不可变数据、纯函数、少副作用 | 易测试、易推导、利于并行 | 热路径拷贝和状态转移表达成本 | 数值计算、规则求值、回放推导 |
| STM / 身份与状态分离 | 通过事务或引用治理共享状态 | 降低共享写混乱 | 高争用热路径成本高 | 配置后台、低频协调逻辑、工具系统 |
| Actor | 状态封闭,消息驱动,单 Actor 顺序执行 | 边界清晰、单写者友好 | 热点 Actor、消息风暴、跨 Actor 一致性 | 房间、场景、玩家聚合、战斗权威 |
| CSP / Channel | 顺序进程 + Channel 协作 | 流程拆分清晰、组合性强 | Channel 设计不好会变成隐式阻塞网 | 网关、会话、流水线、内部协调链路 |
| 数据并行 | 同类数据批量并行处理 | 吃满 CPU / SIMD / Job System | 不擅长复杂共享状态流程 | ECS、动画、寻路批处理、碰撞、渲染前处理 |
| Lambda / 数据分层 | 实时层 + 批处理层分工 | 同时兼顾实时近似与离线精算 | 双链路治理成本高 | BI、审计、推荐、风控、画像系统 |
这张表最重要的不是记术语,而是看清楚:同样叫“并发”,有的模型在治理共享状态,有的模型在治理流程,有的在治理批量计算,有的在治理数据分层。
哪几类模型更接近游戏主运行时
如果只看核心游戏运行时,而不是整套项目所有系统,真正经常进入主链路的通常是:
- 单线程 / 单写者思路。
- Actor。
- CSP / Channel 配合状态机。
- 数据并行用于热路径批处理。
相反,下面这些更常见于局部层或外围层:
- 函数式思路,用于规则求值、回放和配置处理。
- STM / 身份与状态分离,用于工具、后台和低频共享状态。
- Lambda / 数据分层,用于 BI、风控和离线分析。
这个区分很重要,因为很多团队的问题不是模型不知道,而是把适合外围系统的模型,错放到了高时序敏感的核心链路上。
一个更贴近游戏项目的判断方法
讨论并发模型时,先不要问“哪个先进”,先问下面几件事:
- 这份状态是否允许多人同时写。
- 这条链路是否必须保持顺序可推理。
- 这里的主要压力是共享状态冲突、异步流程复杂,还是批量计算吞吐。
- 故障最怕的是不一致、抖动,还是吞吐不够。
这些问题答清楚以后,大多数模型选择其实会自然收敛。
常见误区
- 把并发模型理解成语言或框架口味之争,而不是状态治理方式之争。
- 试图让一个模型覆盖整套游戏系统,结果主链路和外围链路都不合适。
- 只看理论并行能力,不看时序可推理性、恢复难度和线上排障成本。
这一页真正想建立的,不是哪种模型最好,而是一套判断并发问题该落在哪一层的坐标系。
实战案例:一次「一个模型打天下,全服越改越抖」的并发立场错配排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「先把常见并发立场摆出来」「一张更有用的总览表」「一个更贴近游戏项目的判断方法」用到深水区:选的是「最先进的」,抖的是主链路——这是立场没摆全、模型错放位置的排查,也是「先问问题再选模型」的完整展开。
背景与现象:某团队用一套 Actor 框架覆盖全服 4 类链路(战斗主链路、引擎热路径、会话接入、BI 审计),理由是「Actor 最先进」。战斗高峰时热点场景 Actor 单帧排队 35 毫秒,帧 P99 涨到 48 毫秒(预算 16 毫秒);同一批 BI 日更任务和对局结算挤在同一条在线链路上,结算延迟 6 小时。
第一步:现象与口径——先分清哪条链路在抖
影响面口径:帧 P99 48 毫秒超预算三倍,掉帧投诉集中在战斗高峰;结算与日更走同一套 Actor 调度,晚高峰互相挤占。
选型口径:选型会议记录里只有「哪个更先进」的争论,没有一条针对状态、顺序、压力与故障的判断——四问一个都没答。
第二步:分层归因——按本页判断方法四问排查
全服越改越抖归因 占比
一套模型覆盖 4 类链路、外围模型放进主链路 56%
热点 Actor 单写排队 27%
BI 审计批处理挤在在线链路 17%逐项比对:四类链路被同一套模型盖住,本页说得直白——很多团队的问题不是模型不知道,而是把适合外围系统的模型,错放到了高时序敏感的核心链路上;场景主 Actor 单写排队;BI 日更与结算挤在线上链路。三类都在,错放位置那条占比最大。
第三步:根因——立场没摆全,选型退化成信仰之争
只讲「单线程、多线程、Actor、协程」几个词,立场不摆全,选型会退化成信仰之争——各自熟悉的立场各说各话,谁职级高听谁的。四个判断问题——这份状态是否允许多人同时写、这条链路是否必须保持顺序可推理、主要压力是共享状态冲突还是异步流程还是批量吞吐、故障最怕不一致还是抖动还是吞吐——一个都没答,模型选择没有收敛依据。
第四步:处置与回填——按坐标系重新摆位置
- 立场摆全:把七类并发立场和总览表搬进选型会,每个候选模型对位「最强价值 / 主要代价 / 更适合什么」三栏。
- 主链路收口:战斗主链路只保留单写者、Actor、状态机与数据并行;BI、审计、推荐迁出到 Lambda / 数据分层链路。
- 四问前置:任何并发选型先答四个判断问题,答不清楚不进评审。
复核账:帧 P99 48 毫秒 → 16 毫秒;热点 Actor 单帧排队 35 毫秒 → 4 毫秒;结算延迟 6 小时 → 40 分钟;4 类链路全部过四问评审。
回填清单:
| 回填项 | 动作 | 回填处 |
|---|---|---|
| 立场坐标图 | 七类立场逐条对位「最强价值 / 主要代价 / 更适合什么」,再进选型 | 一张更有用的总览表 |
| 主链路候选边界 | 战斗主链路只进单写者、Actor、CSP 状态机与数据并行 | 哪几类模型更接近游戏主运行时 |
| 四问定收敛 | 选型前先答多写、顺序、压力、故障四问 | 一个更贴近游戏项目的判断方法 |
| 误区评审哨兵 | 禁口味之争、禁一模盖全服、禁只看理论并行 | 常见误区 |
案例的三个教训
- 先进不等于匹配。并发模型是状态治理方式之争,不是语言或框架口味之争,答不出四问的选型都是信仰之争。
- 错放位置比选错模型更常见。适合外围系统的模型放进高时序敏感的主链路,抖动是必然结果。
- 混合体才是常态。游戏项目真正做的不是选一个模型,而是把不同模型放到不同位置,主链路和外围各归其位。
Used By
并发方案选型会议的立场盘点;团队公共语言对齐。
- skynet(commit
64391f7):线程池 × 服务队列两级调度——worker 线程从全局队列取出某个服务的队列后连续分发(skynet-src/skynet_start.c:157、:167),服务上下文把队列排空、逐条调用消息处理(skynet-src/skynet_server.c:283-289)。服务数量再多,同一服务同一时刻也只有一个线程在跑:「单线程可推理」是调度结构给的,不是约定给的
Related
关系链:立场罗列 → 游戏工程特殊性 → 总览表 → 主运行时候选 → 判断方法。上游:无(runtime 入口)。下游:runtime/01(三种状态组织哲学)。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。