Skip to content

C++ 并发与多线程专题 ​

C++ 后端/客户端面试的第二部分经常考多线程:std::thread、mutex、condition_variable、atomic、数据竞争、死锁。C++ 本身不考操作系统底层那么深,重点在「使用正确 + 原理」。

一、核心概念 ​

  • 线程与进程:进程是资源分配单位、线程是调度单位;同进程线程共享地址空间(堆/全局),各自独立栈。
  • 数据竞争(data race):两个及以上线程同时访问同一内存,至少一个在写且未同步 → UB。
  • 竞态条件(race condition):执行结果依赖交错顺序,即使无数据竞争也不一定正确。

二、常用同步工具 ​

工具用途
std::mutex / lock_guard / unique_lock互斥,RAII 自动解锁
std::atomic<T>原子变量(lock-free for 常用整数)、内存序
std::condition_variable条件等待 / 通知
std::future / std::promise一次性返回值通道
std::async异步任务
std::thread / jthread(C++20)线程,join 管理
shared_mutex(C++17)读写锁
call_once只执行一次

三、高频自测(含答案) ​

1. 多线程加锁怎么写?lock_guard vs unique_lock?

答案

lock_guard 构造即锁、析构即解锁、不可手动解锁,轻量;unique_lock 可延迟加锁/手动解锁/配合条件变量,更灵活但略重。详见 threads-basics.md。

2. 什么是死锁?四个必要条件 + 如何避免?

答案

互斥、占有且等待、不可剥夺、循环等待四条件同时成立才死锁。破坏其一即可:按固定顺序加锁、std::lock 一次锁多个、try_lock、RAII、设超时。详见 deadlock-singleton.md。

3. 条件变量怎么用?为什么必须配合锁?虚假唤醒?

答案

wait 会释放锁并阻塞、被 notify 唤醒后重新加锁;必须配锁因为判断条件要原子(条件与共享数据相关)。虚假唤醒可能发生,所以要用 while (条件不满足) wait(...) 重查。

4. 手写线程安全单例?

答案

C++11 起局部 static 初始化天然线程安全(Magic Static)最简单;或 call_once;双重检查锁定需配合 atomic + 正确的内存序。详见 deadlock-singleton.md。

5. std::atomic 的内存序?

答案

seq_cst 最强、全局一致顺序;acquire/release 成对建立 happens-before;relaxed 只保证原子性、无顺序。按需选更弱序换取性能。详见 atomic-memoryorder.md。

6. volatile 与 atomic 的关系?

答案

无关。volatile 只阻止编译器优化该变量的读写(用于硬件/信号),不保证原子性、可见性、顺序;atomic 保证原子操作和内存序。并发必须用 atomic 而非 volatile。

7. 生产者-消费者模型实现?

答案

互斥锁 + 条件变量(not_empty/not_full)+ 有界缓冲;或无锁环形队列。详见 threads-basics.md。

8. 数据竞争如何用工具检测(TSan)?

答案

ThreadSanitizer,编译加 -fsanitize=thread,运行期检测数据竞争并报告位置。详见 threads-basics.md。

子文档 ​

文件内容
hpc-intro.mdHPC 并发总览(线程数/Amdahl/避免争用)
threads-basics.md线程、mutex、条件变量、生产者消费者
atomic-memoryorder.mdatomic 与内存序、volatile 对比
deadlock-singleton.md死锁、单例、锁的选择
thread-pool.md线程池(含工作窃取、手写)
lockfree.md无锁编程(CAS/ABA/hazard)
lock-optimize.md锁优化、假共享、自旋 vs 互斥、读写锁
parallel-algo.md并行算法、分治、任务图/流水线
coroutine.mdC++20 协程 co_await/机制
手写并发件手写 SPSC 无锁环 / 有界阻塞队列 / 单写多读快照发布(跨到 C++ 手写专题)

糌粑的面经笔记 · VitePress 版