并发与性能
并发是为了重叠等待(I/O、锁、计时器),不是为了「更多线程 = 更快」。本篇归纳模型选择、常见故障模式,以及可重复的测量步骤。
并发模型对照
| 模型 | 机制 | 适合 | 注意 |
|---|---|---|---|
| 多线程 + 共享内存 | OS 线程、锁、条件变量 | CPU 并行、细粒度共享状态 | 死锁、伪共享、优先级反转 |
| 协程 / async-await | 协作式调度、事件循环 | 高并发 I/O(HTTP、DB) | 阻塞调用会卡住整个 loop |
| Actor / 消息 | 邮箱、无共享可变状态 | 分布式、状态机 | 背压、消息顺序、超时 |
| 数据并行 | 分片、map-reduce、GPU | 批处理、矩阵 | 拆分成本、内存带宽 |
Swift:async/await + Task + actor;UI 相关状态用 @MainActor。
TypeScript/Node:单线程 event loop;CPU 密集用 worker 线程或下沉到原生。
Go:goroutine + channel;注意泄漏与无缓冲 channel 死锁。
正确性:三件事
- 互斥:同一可变状态同时只有一个写者(或读者-写者规则明确)。
- 可见性:一个线程的写入对另一个线程可见(内存模型、
@MainActor、volatile 的误用)。 - 有序:happens-before 关系成立(锁、channel 发送、atomic)。
注意
数据竞争:未同步的并发读写是未定义行为(Rust/Swift 编译期尽量拒绝;C/C++ 仍可能发生)。优先用类型系统与 actor 隔离,而不是「多加一把锁」。
性能排查流程
| 步骤 | 动作 | 工具示例 |
|---|---|---|
| 1. 定义指标 | p95 延迟、吞吐、错误率、内存峰值 | SLI/SLO、产品埋点 |
| 2. 复现负载 | 接近生产的 QPS、payload、冷/热缓存 | k6, wrk, 自定义脚本 |
| 3. Profile | 找热点函数与等待 | Instruments, perf, py-spy |
| 4. 分类 | CPU / 分配 / 锁 / I/O / 外部依赖 | 火焰图 + 追踪 span |
| 5. 改动 | 一次只改一类假设 | 前后对比 + 回归测试 |
快路径原则:先减等待(批处理、连接池、缓存、异步 I/O),再减计算(算法、SIMD、原生扩展)。
反模式清单
| 反模式 | 症状 | 改法 |
|---|---|---|
| 线程池过大 | 上下文切换飙升 | 按 CPU 核数与阻塞比例调参 |
| 全局可变单例 | 偶发崩溃、脏读 | 注入依赖、actor、不可变快照 |
| async 里阻塞 | 延迟尖刺 | await 专用 API、线程池 offload |
| 无界队列 | 内存暴涨、延迟雪崩 | 背压、限流、丢弃策略文档化 |
| 过早并行化 | 复杂度↑收益≈0 | Amdahl:先 profile 串行瓶颈 |