应使用 std::lock_guard 而非手动 lock()/unlock(),因其通过 RAII 在作用域结束时自动释放锁,避免异常或提前 return 导致的死锁。

直接结论:用 std::lock_guard 包裹临界区,而不是手动调用 lock() 和 unlock() —— 否则只要抛异常或提前 return,就大概率死锁。
为什么不能裸用 lock()/unlock()
裸调用看似直观,但实际极难写对:
- 一旦临界区内抛出异常(比如
std::cout失败、内存分配失败),unlock()就不会执行,锁永远卡住 - 多个 return 路径时容易漏掉某处
unlock() - 即使当前逻辑没异常,后续维护者加一行 new 代码也可能引入风险
这不是理论风险 —— 生产环境里因裸锁未释放导致的线程挂起,往往要靠 core dump + gdb 逆向才能定位。
std::lock_guard 是怎么自动管理的
它本质是一个 RAII 守卫:构造时调用 mtx.lock(),析构时自动调用 mtx.unlock()。作用域一结束(无论是正常落括号、return 还是 throw),锁立刻释放。
立即学习“C++免费学习笔记(深入)”;
示例关键片段:
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> guard(mtx); // ← 构造即加锁
++counter; // ← 临界区
} // ← 作用域结束,guard 析构,自动 unlock()
}
注意:guard 必须定义在临界区**开始前**,且生命周期覆盖整个临界区;不能写成 std::lock_guard<:mutex>(mtx)</:mutex> 这种临时对象(会立即析构)。
什么时候该换用 std::unique_lock
std::lock_guard 简单够用,但以下场景必须换:
- 需要延迟加锁(比如先做条件判断,再决定是否进临界区)→ 用
std::unique_lock的默认构造 +lock() - 需要在临界区内主动
unlock()做长时间 I/O,再重新lock()→std::unique_lock支持中途释放 - 要配合
std::condition_variable::wait()→wait()只接受std::unique_lock
简单说:std::lock_guard 是“进门前锁门、出门自动锁”,std::unique_lock 是“带钥匙的门,可随时开合”。别为省事在 wait 场景硬套 lock_guard,编译都过不去。
容易被忽略的初始化与生命周期问题
互斥量本身不是线程安全的初始化目标:
-
std::mutex对象必须在任何线程访问前完成构造 —— 全局/静态std::mutex mtx;没问题;但局部 static mutex 在多线程首次调用时可能触发竞态(C++11 起已保证静态局部变量初始化线程安全,可放心) - 千万别把
std::mutex成员放进可被拷贝的类里 ——std::mutex是不可拷贝、不可移动的,否则编译报错use of deleted function - 如果临界区逻辑很长,考虑拆细或改用
std::atomic(仅适用于简单读写,如计数器)—— 锁越短越好,别让一个std::lock_guard包住整个函数体
最常踩的坑其实是:把锁对象和被保护数据放在不同作用域,或者误以为 std::mutex 能保护所有同名变量 —— 它只保护你显式用它守卫的那段代码块。


















