C++面试需知——并发与多线程
并发与多线程 (C++11/14/17/20)
1. 基础概念
*问题类型:
-
进程(Process)和线程(Thread)的区别?
特性 进程 线程 定义 资源分配的最小单位(独立程序实例) CPU调度的最小单位(进程内的执行流) 资源占用 独立地址空间、文件描述符、全局变量 共享进程资源(堆、全局变量) 创建开销 高(需复制或写时复制资源) 低(仅需分配栈和寄存器) 隔离性 高(崩溃不影响其他进程) 低(线程崩溃可能导致整个进程终止) 通信方式 管道、消息队列、共享内存、信号 直接读写共享内存(需同步) 切换成本 高(需切换页表、刷新TLB) 低(仅切换寄存器、栈) 关键比喻:
- 进程 = 独立工厂(拥有独立资源和生产线)
- 线程 = 工厂内工人(共享工厂资源,协作完成任务)
-
并发(Concurrency)和并行(Parallelism)的区别?
概念 并发 并行 核心目标 同时处理多任务(逻辑上) 同时执行多任务(物理上) 依赖硬件 单核即可实现(时间片轮转) 需多核/多CPU 实现方式 线程切换、异步I/O 多线程分派到不同核心 关系 并发包含并行(并行是并发的子集) 并行是并发的最高效形式 示例:
- 并发:单核CPU处理10个网络请求(交替执行)
- 并行:8核CPU同时渲染8帧视频
-
线程安全(Thread Safety)和数据竞争(Data Race)?
问题 定义 解决方案 线程安全 多线程访问时行为正确(如无数据污染) 同步机制(互斥锁、原子操作) 数据竞争 多线程无同步访问共享数据且至少1次写 互斥锁( std::mutex
)、原子变量(std::atomic
)数据竞争示例:
int counter = 0; // 共享变量 void unsafe_increment() { counter++; // 非原子操作 → 数据竞争 } // 两个线程同时调用 unsafe_increment() 导致结果不可预测
修复方案:
std::mutex mtx; void safe_increment() { std::lock_guard<std::mutex> lock(mtx); counter++; }
-
死锁(Deadlock)的条件和避免策略?
-
四个必要条件(缺一不可):
- 互斥:资源独占使用
- 请求保持:持有资源时请求新资源
- 不可剥夺:资源只能主动释放
- 循环等待:线程间形成等待环
-
避免策略:
策略 实现方式 加锁顺序 所有线程按固定顺序获取锁 锁超时 try_lock_for()
超时放弃锁死锁检测 运行时监控锁依赖图(如数据库系统) 资源分级 将资源分层,禁止跨层请求
经典死锁场景:
// 线程1:lock(A); lock(B); // 线程2:lock(B); lock(A); // 可能死锁
-
-
活锁(Livelock)和饥饿(Starvation)?
问题 原因 特点 活锁 线程不断“让出资源”导致任务无法推进 类似死锁,但线程仍在运行(如反复重试) 饥饿 低优先级线程长期得不到资源(CPU/锁) 部分线程有进展,个别线程被“饿死” 活锁示例:
// 两人在走廊相遇,同时让路又再次阻塞对方 while (collision_detected) { move_left(); // 对方也同时 move_left() → 仍碰撞 move_right(); // 对方也 move_right() → 无限循环 }
饥饿解决方案:
- 公平锁(
std::mutex
无法保证 → 需用队列或std::shared_mutex
) - 优先级调整(如 Linux 的
nice
值)
关键总结
- 进程:资源隔离的独立执行环境
- 线程:轻量级执行流(共享内存,需同步)
- 并发问题核心:
- 数据竞争 → 同步控制
- 死锁/活锁 → 设计无锁结构或严格锁序
- 饥饿 → 公平调度机制
- 公平锁(
2. C++11 线程库
*问题类型:
-
std::thread
的创建、管理和推理?操作 方法 说明 创建线程 std::thread t(func, args...)
启动线程执行函数 func
移动线程 std::thread t2 = std::move(t1)
线程所有权转移( t1
不再管理线程)销毁线程 析构函数自动调用 若线程可联结(joinable)则 std::terminate
生命周期规则:
-
线程对象销毁前必须调用
join()
或detach()
-
错误示例:
{ std::thread t([]{ /*...*/ }); } // 析构时 t 仍 joinable → 程序崩溃
-
-
join()
和detach()
的区别?方法 行为 后续操作 使用场景 join()
阻塞当前线程直到目标线程结束 线程资源可安全回收 需等待结果的任务 detach()
分离线程(后台运行) 失去线程控制权 长时间运行的后台任务 关键限制:
detach()
后无法再操作线程(如强制终止)- 分离线程不能访问局部变量(可能已销毁)
-
互斥量派
std::mutex
及其生类(std::recursive_mutex
,std::timed_mutex
等)?互斥量类型 特性 适用场景 std::mutex
基本互斥锁(不可递归) 通用场景 std::recursive_mutex
可递归加锁(同一线程多次加锁) 递归函数中的共享资源保护 std::timed_mutex
支持超时加锁( try_lock_for/until
)避免死锁的等待操作 std::shared_mutex
(C++17)读写锁(共享读/独占写) 读多写少场景(如缓存) -
std::lock_guard
和std::unique_lock
的作用和区别?特性 std::lock_guard
std::unique_lock
锁管理 RAII 自动加锁/解锁 同左 + 支持手动控制 灵活性 低(构造即加锁) 高(延迟加锁/提前解锁) 性能开销 低(无额外状态) 稍高(维护锁状态) 适用场景 简单作用域保护 条件变量配合/需转移锁所有权 示例对比:
// lock_guard { std::lock_guard<std::mutex> lock(mtx); // 立即加锁 // 临界区 } // 自动解锁 // unique_lock std::unique_lock<std::mutex> lock(mtx, std::defer_lock); lock.lock(); // 手动加锁 lock.unlock(); // 可提前解锁
-
条件变量
std::condition_variable
的作用和使用场景(生产者-消费者模型)?-
作用:线程间同步(阻塞等待条件成立)
-
经典场景:生产者-消费者模型
// 生产者 { std::lock_guard<std::mutex> lock(mtx); queue.push(data); cv.notify_one(); // 唤醒一个消费者 } // 消费者 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [&]{ return !queue.empty(); }); // 防止虚假唤醒 auto data = queue.front(); queue.pop();
-
-
原子操作
std::atomic
的作用?-
作用:无锁线程安全操作(硬件级原子指令)
-
优势:
- 免锁高性能(如计数器)
- 避免数据竞争(
counter++
安全)
-
示例:
std::atomic<int> counter(0); void safe_increment() { counter.fetch_add(1, std::memory_order_relaxed); }
-
-
std::future
和std::promise
的作用?组件 作用 关系 std::promise
存储异步结果(生产者) 与 future 共享状态 std::future
获取异步结果(消费者) 从 promise 或 async 获取结果 工作流程:
std::promise<int> p; std::future<int> f = p.get_future(); // 生产者线程 std::thread t([&p]{ p.set_value(42); }); // 消费者 int result = f.get(); // 阻塞等待结果 t.join();
-
std::async
的作用和使用?-
作用:异步执行函数并返回
future
-
启动策略:
std::launch::async
:强制新线程执行std::launch::deferred
:延迟执行(调用get()
时运行)
-
示例:
auto future = std::async(std::launch::async, []{ return compute_heavy_task(); }); auto result = future.get(); // 阻塞获取结果
-
-
线程池的实现原理?
-
核心组件:
- 任务队列:存储待执行函数(
std::function<void()>
) - 工作线程组:固定数量线程循环取任务执行
- 同步机制:互斥锁 + 条件变量
- 任务队列:存储待执行函数(
-
工作流程:
while (running) { std::unique_lock<std::mutex> lock(queue_mutex); cv.wait(lock, [&]{ return !tasks.empty() || !running; }); if (!running) break; auto task = std::move(tasks.front()); tasks.pop(); lock.unlock(); task(); // 执行任务 }
-
优势:
- 避免线程创建/销毁开销
- 控制并发度(防止资源耗尽)
-