直接调用 mutex::lock()/unlock() 危险,因异常、提前 return 或分支遗漏会导致死锁或资源永久占用;C++ 无 finally 机制,无法保证 unlock() 执行。

为什么直接用 mutex::lock() 和 unlock() 很危险
手动配对调用 lock() 和 unlock() 看似简单,但只要中间抛异常、提前 return 或逻辑分支遗漏,就会导致死锁或资源永久占用。C++ 没有“finally”语义,无法保证 unlock() 一定执行。
常见错误现象:std::system_error: resource deadlock would have occurred、程序卡死在某次 lock()、多个线程反复争抢却无进展。
实操建议:
- 永远避免裸写
mtx.lock()/mtx.unlock() - 哪怕只有一行临界操作,也必须用 RAII 封装
- 把
mutex声明为类成员时,注意它不可拷贝——别试图传值或放入std::vector(会编译失败)
lock_guard 的正确用法和常见误用
lock_guard 是最轻量的 RAII 封装,构造即加锁,析构即解锁,不支持转移、不支持延迟锁定,适合绝大多数简单场景。
立即学习“C++免费学习笔记(深入)”;
示例:
std::mutex mtx;
int counter = 0;
void increment() {
std::lock_guard lock(mtx); // 构造时自动 lock()
++counter; // 临界区
} // 出作用域自动 unlock() —— 即使这里 throw 异常也安全
容易踩的坑:
- 把
lock_guard声明成指针(new std::lock_guard<...>(mtx)):析构不会被自动触发 - 跨作用域传递
lock_guard对象:它不可移动也不可复制,编译报错use of deleted function - 在 if 分支里声明,但临界区逻辑实际需要覆盖整个函数:作用域太小,锁过早释放
什么时候该换 unique_lock 而不是硬撑 lock_guard
lock_guard 够用就别换。只有当你需要以下能力时,才考虑 unique_lock:
- 延迟加锁(构造时不锁,后面调
lock()或try_lock()) - 手动控制解锁时机(比如临界区后半段不需要锁,提前
unlock()提高并发度) - 配合条件变量
std::condition_variable::wait()(它要求传入unique_lock) - 需要把锁对象移出作用域(例如返回、存入容器、传参)
性能影响:两者底层都只是封装了 mutex 操作,unique_lock 多一个布尔标记位,开销几乎可忽略;但滥用会增加理解成本和出错概率。
全局 mutex 和静态局部 mutex 初始化陷阱
全局或静态 std::mutex 对象在 C++11 及以后是线程安全初始化的(即首次使用前完成构造),但前提是不能在动态初始化阶段(如其他全局对象的构造函数中)访问它——否则可能触发未定义行为。
更稳妥的做法是用静态局部变量延迟初始化:
std::mutex& get_counter_mutex() { static std::mutex mtx; // 首次调用时线程安全构造 return mtx; }注意:
static std::mutex 在函数内定义是安全的;但static std::mutex*并不解决初始化问题,反而引入指针生命周期管理负担。真正容易被忽略的是:多个互不相关的全局
mutex如果按不同顺序在多线程中首次访问,仍可能引发初始化竞态——所以尽量避免在全局对象构造器里触碰任何mutex。










