lock_guard的核心机制是构造时加锁、析构时解锁,依赖RAII确保异常安全;不可拷贝但可移动(极少使用);必须栈上具名声明,禁用堆分配与临时对象绑定;不支持延迟或尝试加锁,需此类功能应选unique_lock。

lock_guard 构造时加锁、析构时解锁是核心机制
它不靠魔法,只靠 C++ 对象生命周期的确定性:构造函数里调用 mutex.lock(),析构函数里调用 mutex.unlock()。只要对象离开作用域(比如函数返回、异常抛出、大括号结束),析构自动触发,锁必然释放。
这意味着你不需要手动写 unlock(),也不用担心忘了写或被异常跳过——RAII 把资源管理绑定到栈对象的生存期上。
为什么不能拷贝但能移动(C++11 起)
lock_guard 的拷贝构造函数和赋值操作符都被显式删除了(= delete)。这是有意设计:一个锁只能由一个 lock_guard 独占管理,否则多个对象析构时重复 unlock 会 UB(未定义行为)。
不过从 C++11 开始支持移动语义,但实际中几乎不用移动 lock_guard,因为它的典型生命周期很短(作用域内),且移动后原对象处于无效状态(内部 mutex 指针为空),再析构也安全——但没必要。
立即学习“C++免费学习笔记(深入)”;
- 常见错误:
lock_guard<mutex> g1(m); lock_guard<mutex> g2 = std::move(g1);</mutex></mutex>——g1失效,g2接管锁;但g1后续析构不做事,g2析构才真正 unlock - 真实场景中直接声明在作用域开头即可,无需移动
别传引用或指针给 lock_guard,否则 RAII 失效
如果把 lock_guard 放在堆上(new lock_guard<mutex>(m)</mutex>),或者用引用延长生命周期(auto& g = lock_guard<mutex>(m)</mutex>),会导致析构时机失控:前者要手动 delete,后者绑定的是临时对象,可能立即析构。
RAII 依赖栈对象的自动析构,所以必须保证它是具名的、栈上的局部变量。
- 正确:
std::lock_guard<:mutex> g(m);</:mutex> - 危险:
auto&& g = std::lock_guard<:mutex>(m);</:mutex>—— 绑定临时对象,g析构即释放锁,通常比预期早得多 - 更危险:
std::lock_guard<:mutex>* pg = new std::lock_guard<:mutex>(m);</:mutex></:mutex>—— 忘记delete就死锁
lock_guard 不支持延迟加锁或尝试加锁
lock_guard 构造即阻塞等待加锁,没有超时、不支持 try_lock。如果你需要非阻塞或带超时的锁管理,得换 std::unique_lock。
两者关键区别在于:后者是可移动、可延迟、可手动控制加锁/解锁的“灵活版”,而 lock_guard 是轻量、不可变、“一气呵成”的保守选择。
- 要 try_lock?用
std::unique_lock<:mutex> ul(m, std::defer_lock); if (ul.try_lock()) { ... }</:mutex> - 要超时?用
ul.try_lock_for(...)或ul.try_lock_until(...) - 只是保护一段临界区?
lock_guard更小、更快、更难误用
真正容易被忽略的是:lock_guard 的类型必须和 mutex 类型严格匹配,比如 std::recursive_mutex 需配 std::lock_guard<:recursive_mutex></:recursive_mutex>,混用会编译失败;另外,它不负责初始化 mutex,mutex 必须已构造且未被其他线程持有时才能安全使用。


















