Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

并发编程 / Concurrency

1. C++ 并发路线 / C++ Concurrency Roadmap

2. 线程、任务与生命周期 / Threads, Tasks, and Lifetimes

3. C++ 内存模型与原子操作 / The C++ Memory Model and Atomics

4. 锁、条件变量与并发不变量 / Locks, Condition Variables, and Invariants

5. Future、Promise 与 Async / Futures, Promises, and Async

6. 线程池、背压与协作取消 / Thread Pools, Backpressure, and Cancellation

7. 无锁边界、性能与诊断 / Lock-Free Boundaries, Performance, and Diagnostics

8. C++ 并发综合复习 / C++ Concurrency Review

本页目录

无锁边界、性能与诊断 / Lock-Free Boundaries, Performance, and Diagnostics ​

1. 无锁的进展保证 ​

保证含义
blocking线程可能因锁持有者停顿而无法前进
lock-free系统整体持续有操作完成,单线程可能饥饿
wait-free每个操作在有界自身步骤内完成
obstruction-free单独运行足够久可完成

无锁不等于无等待、无竞争或更快。CAS 失败重试、缓存线争夺和回收扫描可能造成很差尾延迟。

2. 真正难点是内存回收 ​

text
T1 读取 node* p
                     T2 从结构删除 p
                     T2 delete p
T1 解引用 p --------> use-after-free
1
2
3
4

原子化 head 指针只保护指针值更新,不保证节点活着。常见方案:

  • hazard pointer:线程发布正在访问的节点,回收前扫描危险指针;
  • epoch/RCU 风格:延迟到所有旧读者离开 epoch 后回收;
  • 引用计数:需解决获取引用与对象归零之间的竞争,成本也可能高;
  • GC/成熟库:把复杂协议交给经过验证实现。

3. ABA 与版本标签 ​

版本化指针把地址和计数一起 CAS,可降低 A→B→A 误判,但要处理计数回绕、对齐位、原子宽度和平台支持。它也不自动解决节点回收。

4. False sharing ​

text
一条 cache line(例如常见 64B,但不是标准常量)
[counter A][counter B][........]
 Thread A 写 A   Thread B 写 B
逻辑不同变量,却因同一缓存线反复失效
1
2
3
4

C++17 提供 hardware_destructive_interference_size / hardware_constructive_interference_size 提示,但实现可用性和数值需检查。padding 会增加内存,只有 profile 证明热点时使用。

5. 争用、吞吐与尾延迟 ​

总吞吐高可能掩盖某些线程长期饥饿。并发基准至少报告:

  • 线程数扫描;
  • ops/s 与 p50/p95/p99;
  • 读写比例、键分布和临界区长度;
  • CPU 使用、上下文切换、缓存未命中;
  • NUMA 和绑定配置;
  • 调试/发布、优化和 Sanitizer 是否开启。

不要用单线程微基准推断高争用性能,也不要在 Sanitizer 构建上比较绝对性能。

6. ThreadSanitizer ​

典型 Clang/GCC 命令:

bash
clang++ -std=c++20 -O1 -g -fsanitize=thread -fno-omit-frame-pointer app.cpp
1

TSan 动态检测实际执行路径中的数据竞争和部分同步误用。它不能证明无竞态,可能不理解自定义汇编同步,且通常不能与 ASan 同时使用。Windows/MSVC 原生支持情况与 Linux/Clang 不同,应记录实际工具链边界。

7. 第一份报告优先 ​

一次 data race 会破坏后续状态,产生大量次生报告。调试流程:

  1. 固定最小可复现和输入种子;
  2. 从第一份报告的两条冲突栈开始;
  3. 找到对象所有权和预期同步协议;
  4. 修复协议而非加入随机 sleep;
  5. 重跑压力测试和 Sanitizer;
  6. 添加能覆盖该交错的测试/断言。

8. 常见伪修复 ​

  • 把普通变量改为 volatile;
  • 在两次访问间加 sleep;
  • 把所有 atomic 改 relaxed;
  • 只在写端加锁,读端不加;
  • 泄漏节点以“解决”UAF,却没有明确资源上限;
  • 把大锁拆成多锁,却没重新定义不变量和锁顺序。

9. 何时不要手写无锁结构 ​

若团队无法给出线性化点、progress guarantee、内存序证明、回收协议和压力/工具测试,就优先使用 mutex、标准同步设施或成熟库。mutex 在低争用时往往有非常优化的快路径,并能在长等待时让线程休眠。

10. 面试速答 ​

问:lock-free 是否保证每个线程前进?

不保证,只保证系统整体持续完成操作;某线程可能不断 CAS 失败而饥饿。

问:false sharing 是数据竞争吗?

不是,变量可被正确同步且逻辑独立,但共享缓存线导致性能抖动。

问:TSan 没报告能否证明线程安全?

不能,它是动态检测,只覆盖本次运行路径和其理解的同步机制。

11. 自测题与答案 ​

  1. 为什么 atomic head 不能独自保证节点安全?
  2. hazard pointer 的核心思想是什么?
  3. 为什么并发基准必须看尾延迟?
  4. sleep 为什么不能修复 data race?

<details> <summary>参考答案</summary>

  1. 另一个线程可删除并释放已读出的节点,指针值原子不延长对象生命周期。
  2. 读者公开当前可能解引用的节点,回收者仅释放未被任何 hazard 保护的退休节点。
  3. 高吞吐可能伴随个别请求/线程长期等待,用户体验和关闭时延由尾部决定。
  4. 它不建立标准同步关系,只改变某次调度概率,data race 仍是 UB。

</details>

返回并发路线

12. Lock-free 的三层问题 ​

text
1. 原子更新:CAS 是否正确?
2. 线性化:操作在哪一点对外生效?
3. 内存回收:其他线程何时不再持有旧节点?
1
2
3

只写出无锁 push/pop 的 CAS 循环远未完成;节点一旦过早 delete,另一个线程可能仍在读其 next,形成 UAF。

13. 进展保证不是性能排序 ​

text
blocking: 线程可因锁持有者暂停而无法推进
lock-free: 系统整体总有某操作推进,单线程可饥饿
wait-free: 每个操作在有界自身步骤内完成
1
2
3

更强保证通常更复杂,不代表墙钟更快。低争用 mutex 可能在缓存、功耗和尾延迟上优于重试 CAS。

14. Hazard pointer 的基本握手 ​

text
reader load candidate
reader publish hazard=candidate
reader recheck candidate still reachable
reader safely dereference
writer removes node -> retire list
writer scans hazards; unprotected retired nodes can reclaim
1
2
3
4
5
6

发布与重检顺序必须严格,hazard slot 管理也有成本。Epoch/RCU 类方案让线程声明读侧时期,批量延迟释放,但停滞线程会阻碍回收。

15. ABA 与带版本指针 ​

text
head = (A, version 7)
A removed/reused
head = (A, version 9)
CAS expecting (A,7) fails
1
2
3
4

版本位宽会回绕,打包还受指针对齐、平台位数和原子宽度约束。版本标签解决身份变化检测,仍需安全回收保证读取节点本身合法。

16. False sharing 的具象图 ​

text
one cache line
[counter T0][counter T1][counter T2][counter T3]
   core0 writes     core1 writes
cache line ownership repeatedly bounces although logical variables differ
1
2
3
4

可用 alignas/hardware_destructive_interference_size 思路分隔热点,但具体硬件值和标准库支持需验证。填充会增加内存占用,先用性能计数器证明。

17. CAS 失败率是争用信号 ​

记录尝试数/成功数、队列深度、退避次数和 p99。忙等在超额订阅或虚拟机暂停下会浪费核心;指数退避、yield 或阻塞后备可能改善,但会改变公平性与尾延迟。

18. TSan 报告与理论证明 ​

TSan 观察一次执行中的同步和冲突访问,能发现许多 data race;自定义原子协议可能需要工具注解,未走路径不会被检查。即使 TSan clean,ABA、错误线性化、逻辑丢更新和内存序在弱架构问题仍可能存在。

text
formal happens-before/linearization reasoning
 + stress/fuzz/scheduler perturbation
 + TSan
 + production metrics
= 更可信证据
1
2
3
4
5

19. 何时退回锁 ​

  • 无成熟回收方案;
  • 争用低且临界区短;
  • 需要复合不变量与公平性;
  • 团队无法长期维护内存序证明;
  • benchmark 未证明收益;
  • 平台原子能力不统一。

标准/成熟库的并发队列通常优于自写;最好的优化常是减少共享、分片或批处理,而非更复杂 CAS。

20. 连续追问与自测 ​

问:lock-free 是否没有锁? 答:算法进展不依赖互斥阻塞,但实现/特定 atomic 可能受平台支持;术语重点是进展保证。

问:atomic<shared_ptr> 能解决所有回收吗? 答:可简化部分所有权,但引用计数、控制块争用和结构线性化仍需分析。

  1. 线性化点是什么? 答:并发操作可视为瞬间生效的那个原子事件。
  2. hazard pointer 为何要重检? 答:候选可能在读取与发布 hazard 之间已被移除。
  3. false sharing 有 data race 吗? 答:可以没有;它是不同变量共享缓存行导致的性能争用。

9. 真实问题:无锁队列吞吐高但尾延迟失控 ​

基准测试只看平均吞吐。 高竞争时 CAS 重试让少数线程长时间饥饿。 缓存行在核心之间来回转移。 回收扫描又占用工作线程时间。 结果是 p99 延迟远高于互斥队列。 无锁只描述进展保证,不承诺公平或低延迟。 应同时测量失败重试、调度等待和缓存未命中。 基准必须固定线程数、任务大小和 CPU 亲和性。 单线程结果不能代表并发行为。

10. 最小实验:false sharing ​

cpp
struct Counters { std::atomic<long> a{0}; std::atomic<long> b{0}; };
struct Padded { alignas(64) std::atomic<long> a{0}; alignas(64) std::atomic<long> b{0}; };
1
2

两个线程分别递增 a 和 b。 Counters 可能让两个原子共享一条缓存线。 Padded 用对齐把它们分开。 如果吞吐显著变化,说明瓶颈来自缓存一致性流量。 这两个变量没有数据竞争。 不要把性能问题误报成内存模型错误。 缓存线大小应按目标架构验证。 过度填充会增加内存占用和 NUMA 访问成本。

11. ABA 与安全回收 ​

线程 A 读取指针 P 并暂停。 线程 B 移除 P、释放对象,再分配新对象复用同一地址。 线程 A 的 CAS 看到地址仍为 P,就错误地认为状态未变。 这就是 ABA。 增加版本计数可以识别部分复用。 但宽度不足会发生计数回绕。 hazard pointer 让读者发布正在访问的地址。 epoch reclamation 等待所有可能读者离开旧纪元。 引用计数把对象寿命交给控制块,但会产生原子计数争用。 回收协议必须和线性化点一起证明。

12. 诊断工具边界 ​

ThreadSanitizer 擅长发现普通内存数据竞争。 它不能证明无锁算法的线性化正确。 也不能覆盖未执行的调度交错。 AddressSanitizer 可发现部分越界和 UAF。 性能分析器可观察锁等待、上下文切换和缓存事件。 Linux perf、Windows ETW/WPA、Intel VTune 的采样维度不同。 工具开销会改变调度,需要对照基准。 先构造最小复现,再启用高开销检测。 保留编译器、架构和运行参数。

13. 工程实践:何时不用无锁 ​

共享不变量超过一个原子字段时优先 mutex。 需要阻塞等待时,条件变量通常更清晰。 需要公平性时不要依赖 lock-free 保证。 需要安全回收且团队无经验时选择共享所有权。 只有瓶颈经测量确认后才引入 CAS 循环。 记录 CAS 失败率和重试上限。 为饥饿线程提供退避或分片策略。 按生产者和消费者分离缓存线。 发布 ABI 时确认 atomic 类型的对齐和锁实现。 跨 DLL 传递原子对象要统一编译器和运行库。

14. 失败场景复盘 ​

失败一:CAS 循环无退避,核心占用 100%。 修复:指数退避、分片或回退到锁。 失败二:节点删除后立即 free,压力下崩溃。 修复:hazard 或 epoch 回收。 失败三:只看平均吞吐,忽略 p99。 修复:报告分位数和饥饿时间。 失败四:填充常量固定为 64,迁移到其他架构失效。 修复:依据目标平台缓存线和硬件计数验证。 失败五:TSan clean 就宣布算法正确。 修复:补充模型检查、线性化证明和调度压力。

15. 自测题 ​

问:lock-free 是否代表每个线程有界完成?答:不是,那是 wait-free 的目标。 问:ABA 只是指针问题吗?答:任何可复用的状态值都可能出现类似问题。 问:false sharing 是否未定义行为?答:不是,它通常是合法但低效的访问模式。 问:TSan 能证明没有死锁吗?答:不能,它主要检测数据竞争。 问:为什么先测量再无锁?答:无锁增加证明、回收和诊断成本,未必改善真实瓶颈。

16. 基准的最小协议 ​

固定编译器版本、优化级别和 CPU 频率策略。 预热后再采样,避免把初始化算入结果。 报告中位数、p95、p99 和最大值。 记录线程数与核心数的比例。 分别测试低竞争和高竞争输入。 统计 CAS 失败次数和退避次数。 区分吞吐下降与调度抖动。 检查是否有日志、分配和系统调用污染结果。 把互斥版本作为基线。 没有基线就无法判断无锁优化是否值得。

17. 线性化审查 ​

为每个公开操作指出唯一线性化点。 入队可能在线程链接新节点的 CAS 成功处线性化。 出队可能在推进 head 的 CAS 成功处线性化。 失败路径不能泄漏已保留的资源。 帮助者线程修改状态时也要保留调用语义。 状态读取和对象销毁的顺序要独立证明。 测试只能增加信心,不能替代不变量推理。 无法说明线性化点时,优先回到互斥实现。

18. 这一节先记住 ​

无锁并不自动更快,也不自动公平。 ABA 与回收是算法的一部分。 false sharing 是缓存一致性成本,不是数据竞争。 工具提供证据,不替代正确性证明。 以 p99、失败重试和可维护性决定是否采用。

工程深化:把本篇知识落到项目里 ​

这一节不是为了凑篇幅,而是把《无锁边界、性能与诊断 / Lock-Free Boundaries, Performance, and Diagnostics》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。

text
concept -> boundary -> failure signal -> minimal proof -> project rule
1

工程切片 1:最小可复现样例 ​

**场景。**围绕 互斥量 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。

**边界。**先判断这里讨论的是 互斥量、条件变量 还是 临界区 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 互斥量 -> 条件变量 -> 临界区 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 2:接口边界 ​

**场景。**围绕 条件变量 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。

**边界。**先判断这里讨论的是 条件变量、临界区 还是 等待谓词 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 条件变量 -> 临界区 -> 等待谓词 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 3:失败注入 ​

**场景。**围绕 临界区 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。

**边界。**先判断这里讨论的是 临界区、等待谓词 还是 死锁 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 临界区 -> 等待谓词 -> 死锁 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 4:跨平台差异 ​

**场景。**围绕 等待谓词 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。

**边界。**先判断这里讨论的是 等待谓词、死锁 还是 锁粒度 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 等待谓词 -> 死锁 -> 锁粒度 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 5:性能观察 ​

**场景。**围绕 死锁 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。

**边界。**先判断这里讨论的是 死锁、锁粒度 还是 互斥量 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 死锁 -> 锁粒度 -> 互斥量 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 6:生命周期 ​

**场景。**围绕 锁粒度 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。

**边界。**先判断这里讨论的是 锁粒度、互斥量 还是 条件变量 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 锁粒度 -> 互斥量 -> 条件变量 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 7:诊断证据 ​

**场景。**围绕 互斥量 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。

**边界。**先判断这里讨论的是 互斥量、条件变量 还是 临界区 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 互斥量 -> 条件变量 -> 临界区 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 8:维护策略 ​

**场景。**围绕 条件变量 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。

**边界。**先判断这里讨论的是 条件变量、临界区 还是 等待谓词 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 条件变量 -> 临界区 -> 等待谓词 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 9:版本演进 ​

**场景。**围绕 临界区 建一个很小的案例,不要一开始就放进完整业务系统。为未来变更预留兼容策略:版本号、特性位、弃用窗口、迁移脚本和回滚入口。

**边界。**先判断这里讨论的是 临界区、等待谓词 还是 死锁 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 临界区 -> 等待谓词 -> 死锁 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 10:最小消费者 ​

**场景。**围绕 等待谓词 建一个很小的案例,不要一开始就放进完整业务系统。从使用者角度写一个小程序或小工程,只依赖公开接口,验证安装包、头文件和运行时行为。

**边界。**先判断这里讨论的是 等待谓词、死锁 还是 锁粒度 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 等待谓词 -> 死锁 -> 锁粒度 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 11:边界复盘 ​

**场景。**围绕 死锁 建一个很小的案例,不要一开始就放进完整业务系统。用一张表说明问题发生在源码、编译、链接、加载、运行、关闭或发布中的哪一层。

**边界。**先判断这里讨论的是 死锁、锁粒度 还是 互斥量 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 死锁 -> 锁粒度 -> 互斥量 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 12:反例训练 ​

**场景。**围绕 锁粒度 建一个很小的案例,不要一开始就放进完整业务系统。写出一个看似能工作但不可维护的方案,再说明它在规模、团队协作或发布时会怎样失败。

**边界。**先判断这里讨论的是 锁粒度、互斥量 还是 条件变量 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 锁粒度 -> 互斥量 -> 条件变量 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

本篇收束 ​

掌握《无锁边界、性能与诊断 / Lock-Free Boundaries, Performance, and Diagnostics》的标志,不是记住所有名词,而是能把 互斥量、条件变量、临界区、等待谓词、死锁、锁粒度 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

Pager
上一篇6. 线程池、背压与协作取消 / Thread Pools, Backpressure, and Cancellation
下一篇8. C++ 并发综合复习 / C++ Concurrency Review

持续记录,持续成长

Copyright © Tidenflow