C++多线程知识地图

内容介绍

这组笔记的目标不是背 API,而是补上工程里真正需要的并发编程能力。

中国大学课程里常见的 C/C++ 教学通常停在语法、面向对象、STL,较少系统讲并发。但工程代码里,网络服务、后台任务、日志、缓存、批处理、爬虫、音视频、游戏、GUI 都会遇到并发。

学完这条线后,你应该能借鉴 Go 把并发任务组织清楚的思想,但用 C++ 的方式管理线程、内存、生命周期和性能。

这里的重点不是“写得像 Go”,也不是盲目追求某个语言的并发模型,而是形成 C++ 自己的并发判断力:

  • 先拆清楚任务边界,再决定用不用线程。
  • 先明确数据所有权,再决定共享内存还是消息传递。
  • 先设计生命周期和退出路径,再写 std::thread
  • 先保证正确性,再用线程池、背压、批处理优化性能。
  • 能用 C++ RAII 管好资源,就不要把并发代码写成手动收尾的状态机。

这条线会覆盖:

  • std::thread / std::jthread 正确管理线程生命周期。
  • std::mutex / std::lock_guard 保护共享数据。
  • std::condition_variable 实现阻塞等待和通知。
  • std::future / std::promise 表达异步返回值。
  • 用阻塞队列降低共享状态复杂度,并实现背压。
  • 用完成计数模型等待一组任务结束。
  • 用线程池控制并发数量,避免线程爆炸。
  • std::atomic 处理简单无锁状态。
  • std::jthread / stop_token 做协作取消。

可借鉴的任务组织思想

思想C++ 落地方式说明
任务先于线程cpp并发核心思维cpp线程池先识别任务,再选择执行资源
通信优先于到处共享cppChannel模型用阻塞队列传任务/数据,减少共享可变状态
等待关系要显式cppWaitGroup模型cppfuture和promise明确谁等谁、等什么结果
退出路径必须设计cpp协作取消停止标志、关闭队列、stop_token
性能来自资源控制cpp并发性能模型线程数、任务粒度、背压、锁竞争、批处理

注意:C++ 标准线程是系统线程,不具备 Go runtime 那种用户态轻量调度器。C++ 的优势在于资源控制、对象生命周期、内存布局和性能边界更显式;这既更麻烦,也更适合系统级、高性能、低延迟场景。

学习路径

如果第一次看多线程觉得概念散,先读 cpp多线程入门导读。它用“任务、线程、数据、同步、退出”五个问题把后面的页面串起来。

cpp多线程入门导读
  -> cpp并发与多线程
  <-> cpp并发核心思维
  <-> cpp并发性能模型
  <-> cpp线程生命周期
  <-> cpp线程传参与返回值
  <-> cpp互斥锁与RAII
  <-> cpp条件变量
  <-> cpp原子操作
  <-> cppfuture和promise
  <-> cppChannel模型
  <-> cppWaitGroup模型
  <-> cpp线程池
  <-> cpp协作取消

章节导航

  1. cpp多线程入门导读:用任务、线程、数据、同步、退出建立总模型。
  2. cpp并发与多线程:先分清并发、并行、进程、线程,以及什么时候不该开线程。
  3. cpp并发核心思维:建立任务拆分、执行资源、数据所有权、同步通信、退出取消这条主线。
  4. cpp并发性能模型:建立高吞吐并发的工程指标:线程数、任务粒度、背压、锁竞争、批处理。
  5. cpp线程生命周期:掌握 std::threadjoindetachstd::jthread
  6. cpp线程传参与返回值:解决参数复制、引用传递、生命周期、返回值收集。
  7. cpp互斥锁与RAII:用 RAII 管理锁,避免数据竞争和死锁。
  8. cpp条件变量:实现“没数据就睡眠,有数据再唤醒”的阻塞等待。
  9. cpp原子操作:处理计数器、标志位、停止信号等简单共享状态。
  10. cppfuture和promise:获取异步任务结果,理解 asyncfuturepromise
  11. cppChannel模型:用阻塞队列做任务通信和背压,减少共享状态。
  12. cppWaitGroup模型:用完成计数等待一组任务结束。
  13. cpp线程池:控制并发数量,把任务提交给固定数量工作线程。
  14. cpp协作取消:用 std::jthreadstop_token 做可停止任务。

分阶段读法

第一阶段:先能写对

目标是知道线程怎么启动、怎么收尾、怎么安全访问共享数据。

  1. cpp多线程入门导读
  2. cpp并发与多线程
  3. cpp线程生命周期
  4. cpp线程传参与返回值
  5. cpp互斥锁与RAII
  6. cpp条件变量

完成标准:

  • 能解释 joindetachjthread 的区别。
  • 能判断一个 lambda 捕获引用是否可能悬空。
  • 能说清楚“哪个 mutex 保护哪个数据”。
  • 能写出带谓词的 cv.wait(lock, predicate)

第二阶段:能组织任务

目标是把多个任务之间的等待、返回、通信和退出设计清楚。

  1. cpp并发核心思维
  2. cppfuture和promise
  3. cppChannel模型
  4. cppWaitGroup模型
  5. cpp协作取消

完成标准:

  • 能区分任务和线程。
  • 能用 future 表达异步结果。
  • 能用阻塞队列组织生产者消费者。
  • 能设计“关闭队列/通知等待者/等待线程退出”的完整收尾流程。

第三阶段:能控制性能

目标是知道线程不是越多越好,并能控制任务粒度、队列积压和锁竞争。

  1. cpp并发性能模型
  2. cpp原子操作
  3. cpp线程池

完成标准:

  • 能解释线程数、核心数、任务粒度、上下文切换的关系。
  • 知道 atomic 适合简单状态,不适合维护复杂不变量。
  • 能说明为什么大量短任务应该进线程池,而不是每个任务创建线程。

最佳代码实践

  • 优先用 std::jthread,它析构时会自动请求停止并 join
  • 必须共享数据时,用 std::mutex 保护,并用 std::lock_guard / std::unique_lock 管理锁。
  • 跨线程传对象前先回答:谁拥有它,谁能改它,它活到什么时候。
  • 能用消息传递就不要共享可变状态,阻塞队列通常比到处加锁更容易做性能控制。
  • 线程数量要受控。大量短任务交给线程池,不要每个任务都新建线程。
  • 用有界队列做背压,防止生产速度超过消费能力后把内存打爆。
  • CPU 密集任务的线程数通常接近核心数;IO 密集任务再根据阻塞比例增加。
  • 小任务要合批,避免调度开销大于任务本身。
  • 锁的作用域越小越好,不要在持锁期间做 IO、睡眠、复杂计算或调用未知外部函数。
  • 条件变量必须配合谓词使用:cv.wait(lock, predicate)
  • 原子只适合简单状态,不要误以为用了 atomic 就自动线程安全。

常见错误用法

std::thread t([] {
    // do work
});
// 忘记 join 或 detach,std::thread 析构时会 std::terminate
if (!queue.empty()) {
    auto value = queue.front();
    queue.pop();
}
// 多线程下 check-then-act 不是原子操作,中间可能被其他线程改掉
std::mutex m;
m.lock();
do_work();
m.unlock();
// do_work 抛异常时 unlock 不会执行

注意事项

  • C++ 线程是系统线程,创建成本和栈内存成本都远高于用户态轻量任务。
  • 多线程 bug 往往不可稳定复现,必须从设计上减少共享状态。
  • 看到“偶尔错一次”的并发 bug,不要靠加 sleep 修。
  • 并发不是性能银弹。CPU 密集任务要考虑核心数,IO 密集任务要考虑阻塞点。
  • 标准库并发能力足够学习和中小型工程使用,但大型服务常会引入成熟线程池、事件循环或协程框架。
  • Windows 上不要使用过旧的 MinGW.org 工具链学习 std::thread。建议使用 MSVC、LLVM/Clang、MinGW-w64 或 Linux GCC,并按需要添加 -pthread

学习路径