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

本页目录

C++ 并发综合复习 / C++ Concurrency Review ​

本篇是复习入口,细节以六个专题为准。

1. 权威专题 ​

  1. 线程、任务与生命周期
  2. 内存模型与原子操作
  3. 锁、条件变量与不变量
  4. Future、Promise 与 Async
  5. 线程池、背压与取消
  6. 无锁边界、性能与诊断

首次学习从并发路线开始。

2. 一张图串起正确性 ​

text
对象所有权覆盖所有线程/任务
          |
          v
每个共享可变状态有同步协议
          |
          +-- mutex unlock -> lock
          +-- release write -> acquire read
          +-- thread completion -> join
          |
          v
冲突访问之间形成 happens-before
          |
          v
无 data race(仍需检查业务竞态和死锁)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

“无 data race”只是底线。银行转账可能每个字段都加锁却在错误时机检查余额,仍有业务竞态;锁顺序错误还可能死锁。

3. 同步设施选择 ​

问题首选起点
多字段共享不变量mutex + RAII lock
等待状态变化condition_variable + predicate
同时获取多锁scoped_lock / 固定顺序
独立计数/状态atomic,先用 seq_cst
一次初始化函数静态或 call_once
异步单结果future/promise/task
C++20 线程取消jthread + stop_token
资源许可计数C++20 semaphore

4. 高频对比 ​

mutex vs atomic ​

mutex 可保护任意复杂不变量并支持阻塞;atomic 适合单对象状态机或经过证明的无锁协议。atomic 不必然更快,也可能内部用锁。

condition_variable vs atomic wait ​

condition_variable 与 mutex 组合任意谓词和多字段状态;C++20 atomic wait 适合围绕单个原子值的等待。两者都要求业务状态和关闭协议。

thread vs task ​

thread 是执行资源句柄;task 表达要完成的工作和结果。高层业务通常应提交任务,由执行器决定线程。

race condition vs data race ​

data race 是内存模型定义的 UB;race condition 是更宽的时序依赖错误,可以发生在完全加锁的程序中。

5. 条件变量答题模板 ​

  1. 状态由 mutex 保护;
  2. 等待方持 unique_lock;
  3. wait(lock, predicate) 循环检查;
  4. 修改方在锁内更新状态;
  5. notify 提示重新检查;
  6. predicate 包含关闭/取消状态。

条件变量没有通知记忆,predicate 才是事实来源。

6. 内存序答题模板 ​

  1. 先指出 data race 和共享对象;
  2. 画出每线程 sequenced-before;
  3. 指出哪次 acquire 读到哪次 release;
  4. 用传递性推出 payload 写 happens-before 读;
  5. 若无法证明,回到 seq_cst 或 mutex;
  6. 最后才讨论性能。

7. 线程池答题模板 ​

完整线程池至少回答:

  • 有界队列和满载策略;
  • submit/close 原子竞争;
  • 任务异常进入何处;
  • drain 还是 cancel;
  • 运行任务怎样观察取消;
  • 如何唤醒 worker/提交者;
  • 如何避免池内等待死锁;
  • 最终 join 和资源析构顺序。

8. 高频纠错 ​

  • volatile 不是同步;
  • detach 不是后台任务所有权模型;
  • x = x + 1 对 atomic 是 load+store,不是单次 RMW;
  • relaxed 不发布旁边普通数据;
  • future timeout 不取消任务;
  • stop_token 不强杀线程;
  • shared_ptr 保活对象但不保护其成员;
  • lock-free 不等于 wait-free 或更快;
  • TSan 无报告不等于证明正确。

9. 综合自测与答案 ​

  1. 为什么 join 后可读取工作线程写入的普通数据?
  2. 为什么条件变量谓词要包含 closed?
  3. CAS 能避免 ABA 吗?
  4. 队列为空为何不一定代表线程池排空?
  5. 为什么 shared_mutex 可能比 mutex 慢?

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

  1. 线程完成 synchronizes-with 成功 join 返回,形成 happens-before。
  2. 否则关闭时空队列等待者没有可满足条件,会永久睡眠。
  3. 不能,值可能变化后又恢复;需版本标签和安全回收等协议。
  4. worker 可能已取出任务且仍在执行,需要跟踪 in-flight 数量。
  5. 读写状态、更复杂原子操作、公平性和缓存争用都有成本,小临界区不一定受益。

</details>

10. 下一步 ​

继续学习系统与网络编程,观察 C++ 并发协议如何与进程、文件描述符、非阻塞 I/O 和 Reactor 结合。

11. 一次任务从提交到关闭的全链路 ​

text
caller submit(task)
   |
lock queue -> check Running -> enqueue -> notify
   |
worker wait(predicate)
   |
dequeue -> unlock -> execute -> publish result
   |
shutdown: stop accepting -> drain/cancel -> wake all -> join -> destroy state
1
2
3
4
5
6
7
8
9

任何并发组件都应同时回答工作流和关闭流;只实现“正常有任务时怎么跑”,没有处理空队列等待、提交/关闭竞争与在途任务,就不是完整线程池。

12. 数据竞争、逻辑竞态与死锁分别证明 ​

text
data race:冲突非原子访问之间是否有 happens-before?
logic race:即使每次访问原子,检查与行动是否仍被插入?
deadlock:等待图是否可能形成环?
1
2
3

给两个字段都加 atomic 可以消除字段级 data race,但无法让“余额检查 + 扣款”成为事务;这类复合不变量通常用 mutex。

13. 发布数据的三种常见方式 ​

text
mutex: producer unlock -> consumer subsequent lock
atomic: release store -> acquire load that observes it
future: producer sets shared state -> consumer get/wait returns
1
2
3

三者都可建立可见关系,但适用结构不同。不要把 condition_variable 的 notify 当发布本身;共享状态仍需 mutex/atomic 协议。

14. 背压是稳定性的一部分 ​

无限队列把短期吞吐不足变成无限内存增长和延迟:

text
arrival rate > service rate
queue length -> grows -> memory/latency -> failure
1
2

有界队列满时可阻塞提交、返回拒绝、丢弃低优先级、调用者同步执行或降级。策略必须匹配业务,且不能在持有上游关键锁时阻塞造成锁链死锁。

15. 取消分三个层次 ​

text
queued task:尚未开始,可从队列标记/移除
running task:只能协作观察 token
external I/O:还需底层取消 API 与完成竞态协议
1
2
3

“future 超时”只让等待者停止等,不会自动取消任务。取消结果、部分副作用和资源清理要进入任务契约。

16. 性能诊断的因果链 ​

text
低吞吐/高尾延迟
 -> workers busy 还是 blocked?
 -> queue wait、mutex wait、I/O wait 各多少?
 -> 任务粒度是否太小?
 -> cache line 争用/NUMA/false sharing?
 -> 增加线程是否只增加竞争?
1
2
3
4
5
6

平均延迟会掩盖长尾;至少观察 p95/p99、队列深度、拒绝数、活动 worker 与任务服务时间。

17. 综合面试与自测 ​

问:线程池线程越多越好吗? 答:不是。CPU 工作受核心与内存带宽限制,过多线程增加切换/竞争;I/O 工作则结合等待比例和背压测量。

问:notify 是否保存一次信号? 答:condition_variable 不保存事件计数,必须用受保护谓词记录状态。

  1. 为什么关闭先停止接收? 答:避免 join 过程中仍有新任务进入,使终止条件不可达。
  2. 原子计数能否保护队列? 答:不能替代队列多字段结构与元素生命周期协议。
  3. TSan clean 是否证明无逻辑竞态? 答:不能,它主要检测执行覆盖到的数据竞争。

8. 真实问题:偶发错误该从哪里开始 ​

并发故障往往只在特定调度交错中出现。 第一步不是把所有字段改成 atomic。 先找出谁拥有任务、谁拥有状态、谁负责关闭。 然后画出状态转换和等待边。 每条跨线程数据流都应有发布方式。 每个阻塞点都应有唤醒和停止路径。 每个线程都应有 join 或等价的完成确认。 这套问题能把“偶发”变成可审查的协议。

9. 统一直觉:四种关系 ​

所有权回答对象何时销毁。 同步回答写入何时对另一个线程可见。 排队回答过载时工作放在哪里。 取消回答不再需要的工作如何退出。 锁、原子、future 和线程池各自覆盖其中一部分。 没有一种工具自动覆盖全部四种关系。 选择工具前先写清缺的关系。 这比从库名称开始更可靠。

10. 最小设计练习:可关闭队列 ​

队列状态需要 open、closing 和 closed。 生产者只能在 open 时入队。 消费者在队列为空且 closing 时退出。 提交和状态检查在同一把锁内完成。 关闭时唤醒所有消费者和等待提交者。 任务在锁外运行,异常在边界捕获。 join 后才能销毁队列所引用的资源。 这个练习连接了线程、锁、条件变量和生命周期。

11. 选择原语的快速表 ​

单一计数或标志且无复合不变量,可考虑 atomic。 多个字段必须同时一致时,先用 mutex。 需要等待条件变化时,用 condition_variable 或 atomic wait。 需要一次结果和异常传播时,用 future/promise。 需要限制并发和排队时,用线程池。 需要极端性能时,先测量再讨论 lock-free。 不要用一个原子 bool 模拟完整服务关闭。 不要用 detach 回避所有权问题。

12. 内存序复习 ​

普通读写跨线程冲突需要同步。 release 发布之前的写入。 acquire 获取对应发布后的可见性。 relaxed 只保证该原子对象的修改序。 seq_cst 提供更强全局顺序,代价和必要性应测量。 内存序只能约束可见性,不表达业务终态。 原子计数不能替代队列锁。 复杂状态先证明不变量,再降低内存序。

13. 锁与等待复习 ​

锁保护一组不变量,而非一个语句。 条件变量必须配受锁保护的谓词。 notify 不是持久事件。 伪唤醒和超时后都要重检谓词。 多锁使用统一顺序或 scoped_lock。 持锁调用用户代码容易重入和死锁。 锁外执行慢 I/O,锁内只完成状态转换。 关闭时要唤醒所有潜在等待者。

14. 任务和结果复习 ​

thread 句柄必须 join、detach 或转移。 jthread 仍要求入口协作停止。 future 表示一次终态,不等于取消。 promise 的异常要在消费者 get 时报告。 线程池应有容量、拒绝和关闭语义。 池内同步等待同池任务可能饥饿。 取消要覆盖未开始任务和运行任务。 结果、日志和指标要保留任务上下文。

15. 性能与诊断复习 ​

先确定瓶颈是锁等待、调度还是缓存争用。 false sharing 可能发生在完全无数据竞争的程序中。 无锁算法需要安全回收方案。 CAS 失败率和 p99 比平均吞吐更有价值。 TSan 能发现执行到的数据竞争。 ASan 能帮助发现生命周期错误。 性能工具解释样本,仍需结合状态协议。 优化前保存互斥基线和可复现输入。

16. 失败场景练习 ​

把队列容量设为一,并让生产者快速提交。 验证过载被拒绝或阻塞,而不是无限占内存。 让消费者在 wait 前后随机 sleep。 验证谓词不会丢失通知。 让任务抛异常。 验证 worker 不会 terminate 进程。 让关闭与提交并发发生。 验证没有任务进入无人处理的队列。 让线程在停止时阻塞 I/O。 验证存在超时或关闭句柄的唤醒路径。

17. 自测题 ​

问:先选 mutex 还是 atomic?答:先看不变量是否跨多个字段,复杂时从 mutex 开始。 问:如何判断未来是否会死锁?答:维护锁顺序、避免持锁回调,并做并发压力测试。 问:超时后资源能立刻释放吗?答:只有任务已停止或不再借用该资源时才行。 问:无锁队列为何还会崩溃?答:原子替换不等于对象回收安全。 问:最重要的关闭条件是什么?答:不再接收、唤醒等待、任务终态和线程完成都可证明。

18. 这一节先记住 ​

并发正确性由协议组成,不由单个关键字保证。 从所有权、同步、排队和取消四个方向审查设计。 先让关闭可证明,再追求吞吐。 先测量真实瓶颈,再引入更复杂的原语。

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

这一节不是为了凑篇幅,而是把《C++ 并发综合复习 / C++ Concurrency Review》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。

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

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

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

**边界。**先判断这里讨论的是 线程对象、join/detach 还是 共享状态 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程对象 -> join/detach -> 共享状态 -> observable result
1

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

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

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

工程切片 2:接口边界 ​

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

**边界。**先判断这里讨论的是 join/detach、共享状态 还是 生命周期 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> join/detach -> 共享状态 -> 生命周期 -> 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:生命周期 ​

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

**边界。**先判断这里讨论的是 调试日志、线程对象 还是 join/detach 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 调试日志 -> 线程对象 -> join/detach -> observable result
1

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

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

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

工程切片 7:诊断证据 ​

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

**边界。**先判断这里讨论的是 线程对象、join/detach 还是 共享状态 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程对象 -> join/detach -> 共享状态 -> observable result
1

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

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

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

工程切片 8:维护策略 ​

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

**边界。**先判断这里讨论的是 join/detach、共享状态 还是 生命周期 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> join/detach -> 共享状态 -> 生命周期 -> 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:反例训练 ​

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

**边界。**先判断这里讨论的是 调试日志、线程对象 还是 join/detach 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 调试日志 -> 线程对象 -> join/detach -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 线程对象、join/detach 还是 共享状态 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程对象 -> join/detach -> 共享状态 -> observable result
1

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

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

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

本篇收束 ​

掌握《C++ 并发综合复习 / C++ Concurrency Review》的标志,不是记住所有名词,而是能把 线程对象、join/detach、共享状态、生命周期、异常边界、调试日志 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

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

持续记录,持续成长

Copyright © Tidenflow