第 17 章:并发与性能:正确性是性能的前提

17.1 工程问题的本质:快的前提是没错

并发与性能优化有两个目标,但顺序不可颠倒:先正确,后快速。并发下的错误(身份串扰、数据竞争、状态覆盖)比单线程错误更隐蔽、更难复现;性能优化的手段(缓存、并行、预检)一旦破坏正确性,优化的收益全部化为故障成本。

设计原理: 并发与性能的工程形态是"正确性结构 + 延迟预算":先通过结构设计保证并发正确(线程本地、加锁、隔离),再在正确性的约束下做延迟优化(预检、分流、复用)。优化的每一步都必须回答"它破坏了什么正确性前提"。

17.2 并发正确性:共享状态的三种防护

并行路由(多输入并发 + 异常隔离)要求共享状态安全。三种防护手段:

原理: 三者的分工对应三类并发风险的来源:可变实例状态(线程本地)、共享单例(加锁)、全局上下文(contextvars 隔离)。并发正确性的本质是"状态的可见域"——每个状态都要明确"谁能看到、何时看到",并发错误几乎都是可见域失控。

17.3 延迟优化:把模型调用从热路径上拿下来

延迟优化的主线是"减少模型调用、复用已有结果":

原理: 延迟优化的本质是把概率性、高成本的模型调用从高频确定路径上拿下来。规则预检、补充信息收敛、预识别复用,本质都是"确定性手段先行"(与第 9 章识别链路同一原则)。当确定手段能裁决时,模型调用就不该发生——这既是性能原则,也是成本原则。

17.4 并发模型选择与延迟预算方法

17.4.1 Python 并发模型:线程池 vs 协程

框架的并行路由基于 ThreadPoolExecutor。理解这一选择需要回到 Python 的并发约束:GIL 使纯计算型线程无法并行,但 IO 型任务(网络调用、数据库、模型请求)会释放 GIL,线程池在 IO 密集场景仍能并行提速。

维度线程池(框架所选)协程 / asyncio
模型线程封装同步代码事件循环 + 异步语法
侵入性低(业务代码保持同步)高(全链路 async 化)
调试常规async/await 传播难排查
适用IO 密集、调用栈深、混合同步依赖高并发短任务、无阻塞依赖

原理: 框架业务链路包含大量同步组件(规则引擎、审批流、数据库),全部协程化的改造面与调试成本极高。线程池以"低侵入"为代价接受"线程开销",换取业务代码保持同步可调试。并发正确性(线程本地、加锁、隔离,见 17.2 节)正是为这一选择补上的安全网。并发模型的选择取决于调用栈的形态,而非并发的上限。

17.4.2 延迟预算:从 SLO 倒推设计

性能优化的工程方法是延迟预算(latency budget):从用户可感知的端到端 SLO 出发,逐环节分解,使每个环节的优化目标可度量、可问责。

graph LR
    SL["用户 SLO
对话回复 P95 ≤ 5s"] --> E1["入口与安全检测 ≤ 20ms
(规则匹配,无模型调用)"] E1 --> E2["意图识别 ≤ 1s
(规则预检命中即 0ms;模型兜底 ≤ 1s)"] E2 --> E3["路由与上下文 ≤ 10ms"] E3 --> E4["业务处理(Agent) ≤ 3s
(规则裁决 + 模型生成)"] E4 --> E5["返回与流式 ≤ 500ms
(SSE 分块)"]

原理: 预算的价值在"优化有目标、回归可发现"——每个环节的实测值对照预算,超预算即报警,而非等到端到端劣化才察觉。预算还揭示"瓶颈在哪里":若识别强模型兜底占去预算大头,优化优先级就应落在"提高规则预检命中率"上,而非盲目并发。

17.5 失败模式与权衡

失败形态根因设计应对
并发身份串扰实例可变状态共享线程本地存储
全局锁拖垮并行类级锁互斥实例粒度锁
缓存导致脏读缓存无代次校验进程级缓存 + 代次一致性
优化破坏正确性缓存绕过校验校验在优化之前

权衡: 预检优化以"规则层先命中"为代价——若规则误命中,会绕过模型的正确判断。因此预检守卫(动作类命中且句首为查询动词时跳过,保留主谓宾歧义给模型)是必要的:优化的前提是它不会把"该由模型裁决"的输入抢走

17.6 本章小结