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

本页目录

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

1. 线程池解决什么 ​

线程池复用工作线程并集中调度任务,避免每个小任务都创建线程。它不是简单的“queue + vector<thread>”,还必须定义容量、异常、取消、关闭和重入。

text
submitters -> [bounded queue] -> workers -> results
                  ^                 |
                  |                 +-> exception/future
             backpressure
1
2
3
4

2. 状态机 ​

text
Running --close(drain)--> Draining --queue empty/work done--> Stopped
   |
   +--close(cancel)--> Cancelling --queued tasks resolved----> Stopped

Stopped: submit 必须拒绝,所有 worker 已 join
1
2
3
4
5

状态转换应在同一 mutex 下完成。只有布尔 stop_ 往往无法表达“停止接收但排空队列”和“立即取消排队任务”的区别。

3. 有界队列与背压 ​

无限队列把过载转换成无限内存、长尾延迟和过期任务。队列满时可选择:

  • 阻塞提交者(必须可取消,避免锁链死锁);
  • 立即返回 rejected;
  • caller-runs,让提交线程执行以自然减速;
  • 丢弃低优先级/最旧任务(只适合业务允许);
  • 按租户配额和公平调度。

策略是 API 契约,不能在实现中悄悄丢任务。

4. 工作循环 ​

cpp
for (;;) {
    Task task;
    {
        std::unique_lock lock(mutex_);
        cv_.wait(lock, [&] { return state_ != running || !queue_.empty(); });
        if (queue_.empty() && state_ != running) break;
        task = std::move(queue_.front());
        queue_.pop();
        space_cv_.notify_one();
    }
    run_and_capture_exception(task); // 锁外执行
}
1
2
3
4
5
6
7
8
9
10
11
12

实际 drain 条件还需跟踪正在运行任务数,确保“队列空”不等于“所有工作完成”。不要持队列锁执行用户任务。

5. 提交与关闭竞争 ​

提交必须在检查状态和入队之间保持原子性:

text
错误:check running -> close -> enqueue(任务永远无人执行)
正确:同一 mutex 下 check + enqueue;close 也持同一 mutex 改状态
1
2

被拒绝任务的 future 必须得到明确异常/错误,不能留下永不 ready 的共享状态。

6. 取消的层次 ​

  1. 排队前取消:不入队;
  2. 排队中取消:标记并由 worker 跳过,或从队列移除;
  3. 运行中取消:任务观察 token,在安全点退出;
  4. 底层阻塞取消:I/O/条件变量必须有唤醒或超时;
  5. 关闭取消:所有排队任务的结果都要完成为 cancelled。

强制终止线程会破坏锁、对象和事务不变量,标准 C++ 不提供安全的通用强杀线程。

7. 任务所有权与捕获 ​

异步提交后,任务可能晚于调用作用域运行。禁止默认捕获局部引用;按值移动数据、使用明确共享所有权,或保证父作用域等待全部子任务。

cpp
auto data = std::make_shared<Data>();
pool.submit([data, token] { process(*data, token); });
1
2

共享所有权只解决生命周期,不解决 Data 的并发访问。强引用还可能与回调注册形成环。

8. 线程数和任务类型 ​

  • CPU-bound:线程数接近可用 CPU,防止多个池各自过度订阅;
  • blocking I/O:可增大并发,但受连接、文件描述符和内存上限约束;
  • 混合任务:分池、标注 blocking 区域或使用异步 I/O;
  • NUMA/亲和性:属于测量后的高级优化,错误绑定会更差。

hardware_concurrency() 只是提示,容器/虚拟机 CPU 配额可能更低。

9. 任务内等待与线程池死锁 ​

固定大小池中的任务若等待同一池里尚未运行的子任务,所有 worker 都可能被占满:

text
worker 1: parent A 等 child A(在队列)
worker 2: parent B 等 child B(在队列)
队列有工作,但没有空闲 worker -> starvation deadlock
1
2
3

可用工作窃取、help-while-waiting、分层执行器或避免池内同步等待。增加线程数只能掩盖问题。

10. 析构顺序 ​

  1. 停止接收;
  2. 选择 drain/cancel;
  3. 完成取消任务的结果;
  4. notify_all 唤醒 worker 和阻塞提交者;
  5. join 所有 worker;
  6. 最后销毁队列、mutex、cv 和任务依赖资源。

若析构从池自己的 worker 调用,join 自己会失败/死锁。运行时需禁止这种销毁路径或转交外部所有者。

11. 面试速答 ​

问:为什么线程池队列应有界?

有界队列让过载显式反馈,限制内存和排队延迟;无限队列只是延迟失败。

问:drain 与 cancel 的区别?

drain 停止接收但完成已接收任务;cancel 让排队/运行任务按协议取消,并完成相应结果状态。

问:stop_token 是强制取消吗?

不是,它是协作请求,任务和阻塞设施必须主动观察并安全退出。

12. 自测题与答案 ​

  1. 为什么状态检查和入队必须在同一锁下?
  2. 队列空为什么不一定表示 drain 完成?
  3. caller-runs 如何形成背压?
  4. 池内任务等待同池子任务为何会死锁?

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

  1. 否则 close 可插入两步之间,留下无人执行任务。
  2. 仍可能有 worker 正在执行已取出的任务。
  3. 提交线程被迫执行任务,降低继续生产速度。
  4. 所有 worker 都阻塞等待排队子任务,而没有 worker 可取出子任务。

</details>

返回并发路线

13. 线程池首先是一个状态机 ​

text
Running
  | close(drain)             | close(cancel)
  v                          v
Draining                   Cancelling
  | queue empty + active=0    | queued marked cancelled + active finish
  +------------+--------------+
               v
            Stopped -> workers joined -> Destroyed
1
2
3
4
5
6
7
8

状态转换要和队列操作在同一锁下决定,否则 submit 可能在关闭检查后、真正入队前穿过边界,形成永远无人执行的 future。

14. 提交与关闭竞态 ​

错误两步:

text
submit reads running=true
shutdown sets running=false and joins workers
submit enqueues task -> no worker -> future forever pending
1
2
3

正确做法是在同一临界区检查状态并入队,关闭也在该临界区改变状态;提交失败立即返回明确 rejected/broken 结果。

15. 有界队列的等待谓词 ​

text
producer waits: queue not full OR pool not Running
consumer waits: queue not empty OR pool closing
1
2

关闭必须同时唤醒生产者和消费者。阻塞 submit 还要支持 deadline/stop token,避免调用者无法参与系统关闭。

16. 工作循环要在锁外执行任务 ​

cpp
for (;;) {
    Task task;
    {
        std::unique_lock lock(m);
        cv.wait(lock, predicate);
        if (should_exit()) break;
        task = pop();
    }
    task(); // lock released
}
1
2
3
4
5
6
7
8
9
10

否则所有 worker 会串行,任务回调若递归 submit/wait 还可能死锁。active 计数与 drain 条件需在任务完成后以异常安全方式更新。

17. 同池等待为什么会饥饿 ​

text
pool has 4 workers
4 parent tasks each submit child then future.get()
all workers blocked; children queued; no free worker -> deadlock/starvation
1
2
3

解决可采用 continuation、work stealing/帮助执行、任务图调度,或禁止池内同步等待同池子任务。单纯增加线程只推迟问题。

18. 取消不是强制杀线程 ​

运行任务可能已修改外部状态。stop token 只提出请求,任务需在安全点检查并决定回滚/返回部分结果:

text
check token -> do bounded unit -> check token -> ...
1

阻塞系统调用还要使用超时、可取消句柄或事件循环接口,不能等检查点永远到来。

19. Task 捕获的生命周期 ​

队列延长 callable 生命周期,但不自动延长引用捕获对象:

text
submit [&local] -> queued
caller scope ends -> local dead
worker invokes -> UAF
1
2
3

默认移动/复制必要状态进任务;大型共享状态用明确 owner;观察对象用 weak 关系并处理过期。

20. 析构关闭顺序 ​

text
mark closed -> notify all -> request stop -> drain/cancel policy
-> join every worker -> destroy queue/mutex/cv/dependencies
1
2

worker 所用日志器、allocator、回调目标必须活到 join 后。线程对象作为类成员时,成员声明逆序析构要满足这种依赖。

21. 连续追问与自测 ​

问:线程池为何需要有界队列? 答:把过载显式变为背压/拒绝,避免无界内存与延迟。

问:析构时 drain 还是 cancel? 答:是业务契约;关键任务可能 drain,交互预取可 cancel,也可提供两种显式关闭 API。

  1. 提交检查与入队为何原子化? 答:防止关闭穿过两步留下无人执行任务。
  2. 任务为何锁外执行? 答:避免串行化、重入死锁和长临界区。
  3. future 超时是否取消任务? 答:不会,需要独立停止协议。

9. 真实问题:无界队列把过载变成内存爆炸 ​

生产者速度持续高于消费者。 无界队列会不断保存任务闭包和输入数据。 内存压力触发换页后,延迟进一步上升。 最终任务全部超时,重试又继续入队。 线程池必须对容量负责。 有界队列让过载变成阻塞、拒绝或降级。 背压是系统稳定性的一部分,不是单纯性能优化。 提交接口应暴露拒绝原因和队列状态。 调用方不能无限重试 rejected task。

10. 最小实验:有界队列状态机 ​

text
open -> closing -> drained -> stopped
  |       |
  |       +-- reject new submissions
  +---------- accept until capacity
1
2
3
4

open 状态允许提交。 closing 状态拒绝新任务并唤醒等待者。 drained 表示队列为空且运行任务结束。 stopped 表示线程已退出、资源可释放。 提交检查和入队必须在同一锁内完成。 否则关闭线程可能看到空队列并提前退出。 队列容量是任务数还是字节数要明确。 大任务按字节计费更接近内存风险。

11. 取消语义 ​

取消有至少三层含义:未开始任务不执行、运行任务尽快停止、结果不再交付。 队列可以在出队前丢弃带 token 的任务。 运行任务只能协作检查 token。 不可中断的系统调用需要超时或关闭句柄。 取消后的资源清理仍必须执行。 任务应区分取消和失败,便于重试策略判断。 传播 token 时不要保存已经失效的引用。 嵌套任务要把父级停止请求传给子级。 取消回调不能阻塞提交线程太久。

12. 工程实践:线程池设计表 ​

记录 worker 数量、队列容量和拒绝策略。 记录 drain 与 cancel 两种关闭模式。 任务入口统一捕获异常并写入结果。 worker 循环在关闭和空队列条件下使用谓词等待。 任务执行必须在锁外完成。 统计排队时间、执行时间和队列深度。 分离 CPU 池与阻塞 I/O 池。 防止任务在池内同步等待同池任务造成饥饿。 为重入提交设置容量和优先级策略。 进程退出前显式关闭池,不依赖静态析构顺序。

13. 失败场景复盘 ​

失败一:worker 看到 stop 就退出,队列仍有关键任务。 修复:区分 cancel 和 drain 状态。 失败二:提交者等待队列空间时关闭,永远不返回。 修复:关闭时唤醒 not_full 等待者并返回拒绝。 失败三:池内任务等待另一个同池 future,所有 worker 被占满。 修复:避免同步嵌套等待,或预留专用执行资源。 失败四:取消只改原子标志,线程卡在条件变量。 修复:同时 notify_all 或注册停止回调唤醒。 失败五:异常逃出 worker,整个进程 terminate。 修复:worker 顶层捕获并通过 future/日志报告。

14. 自测题 ​

问:有界队列一定提高吞吐吗?答:它主要保护稳定性,吞吐仍由任务和资源决定。 问:取消能否强杀线程?答:标准协作取消不能强杀。 问:为什么提交和关闭要原子化?答:避免任务进入无人处理的状态。 问:队列容量只按任务数够吗?答:任务大小差异大时还需限制字节或成本。 问:worker 越多越快吗?答:超过核心、带宽或下游容量后通常更慢。

15. 排队论直觉 ​

队列长度不是独立指标。 到达速率接近处理速率时,等待时间会急剧放大。 增加线程可能提高处理速率,也可能把瓶颈推到数据库或磁盘。 每个任务占用的内存和下游连接都属于容量预算。 按优先级插队要防止低优先级永久饥饿。 按调用方隔离队列可防止一个租户占满资源。 拒绝应尽早发生,避免做完昂贵解析后才丢弃。 背压信号要能沿调用链向上传播。 监控中同时看提交速率、完成速率和拒绝率。

16. 关闭演练 ​

先创建运行中的慢任务。 同时创建等待队列空间的提交者。 调用 cancel shutdown。 验证等待提交者被唤醒并收到拒绝。 验证运行任务观察 token 后退出。 验证 future 获得明确取消或异常结果。 再调用 drain shutdown。 验证新提交被拒绝而已有任务完成。 关闭返回后线程集合必须全部 join。 这组测试应作为线程池的最低验收。

17. 这一节先记住 ​

队列、worker、取消和关闭必须是一套状态机。 无界队列不是默认安全选择。 future 超时和任务取消是两条不同的控制线。 worker 绝不能在持有队列锁时执行用户代码。 线程数和容量都要靠真实负载测量。

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

这一节不是为了凑篇幅,而是把《线程池、背压与协作取消 / Thread Pools, Backpressure, and Cancellation》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。

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 给出提示?

本篇收束 ​

掌握《线程池、背压与协作取消 / Thread Pools, Backpressure, and Cancellation》的标志,不是记住所有名词,而是能把 任务队列、背压、关闭协议、工作线程、取消令牌、吞吐延迟 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

Pager
上一篇5. Future、Promise 与 Async / Futures, Promises, and Async
下一篇7. 无锁边界、性能与诊断 / Lock-Free Boundaries, Performance, and Diagnostics

持续记录,持续成长

Copyright © Tidenflow