# C++11多线程编程与锁
面试官问:"C++11 的多线程和锁机制了解吗?lock_guard 和 unique_lock 有什么区别?"
多线程是 C++ 后端面试的重头戏。很多人能说出 mutex 和 lock_guard,但问到 unique_lock 为什么能配合条件变量、死锁怎么避免,就答不上来了。关键是理解这些组件之间的配合关系。
# 简要回答
C++11 通过 <thread>、<mutex>、<condition_variable>、<atomic> 四个头文件提供了完整的多线程支持:
- std::thread:创建和管理线程
- std::mutex:互斥锁,保护共享数据
- lock_guard / unique_lock:RAII 锁管理器,防止忘记解锁
- condition_variable:线程间等待-通知机制
- std::atomic:无锁原子操作,适合简单数据同步
# 详细回答
线程管理
std::thread 接受任何可调用对象(函数指针、lambda、仿函数)作为线程入口。创建后必须 join()(等待结束)或 detach()(分离),否则析构时程序终止。
互斥锁体系
std::mutex:最基本的互斥锁,不可重入std::recursive_mutex:同一线程可多次加锁(递归场景)std::timed_mutex:支持超时的互斥锁,try_lock_for()避免永久阻塞
RAII 锁管理
直接用 mutex.lock() / unlock() 容易忘记解锁或异常时泄漏。RAII 包装器解决这个问题:
- lock_guard:构造时加锁,析构时解锁,中间不能手动控制。简单、轻量、够用就用它
- unique_lock:功能更强——支持延迟加锁、手动 lock/unlock、转移所有权。条件变量必须用它,因为
wait()需要临时释放锁
条件变量
condition_variable 实现"等某个条件满足再继续"的模式。典型用法:生产者-消费者。wait() 会原子地释放锁并挂起线程,被 notify_one() / notify_all() 唤醒后重新加锁继续执行。
原子操作
std::atomic<T> 对简单类型(int、bool、指针)提供无锁的原子读写,比 mutex 开销小得多。适合计数器、标志位等简单同步场景,复杂逻辑还是得用锁。

# 知识拓展
面试官可能追问:
Q1: lock_guard 和 unique_lock 有什么区别?
lock_guard 更轻量,构造即加锁、析构即解锁,中间不能动。unique_lock 更灵活,支持延迟加锁(std::defer_lock)、手动 lock/unlock、配合条件变量使用。日常保护临界区用 lock_guard 就够了,需要条件变量或者需要中途解锁时才用 unique_lock。
Q2: 什么是死锁?怎么避免?
两个线程各持有一把锁,又都在等对方的锁,谁也不放手——永久阻塞。避免方法:统一加锁顺序(所有线程都先锁 A 再锁 B)、用 std::lock() 一次性锁多个 mutex、用 RAII 保证异常时也能解锁、用 timed_mutex 加超时兜底。
Q3: 条件变量为什么必须配合 unique_lock?
因为 wait() 内部要做两件事:释放锁(让其他线程能修改条件)和重新加锁(被唤醒后继续操作共享数据)。lock_guard 不支持中途释放锁,只有 unique_lock 能做到。
Q4: atomic 和 mutex 怎么选?
简单的单变量读写(计数器、标志位)用 atomic,零锁开销。涉及多个变量的复合操作(比如同时修改 balance 和 log)必须用 mutex,因为 atomic 只保证单个操作的原子性,不保证多个操作之间的一致性。
评论
验证登录状态...