std::lock_guard不可拷贝也不可移动,因其必须独占绑定一个锁;C++17起删除了移动构造函数,仅支持栈上直接构造;它轻量、高效,适用于简单临界区保护。

std::lock_guard 为什么不能拷贝只能移动?
因为 std::lock_guard 的设计目标是「绑定且独占一个锁对象」,一旦允许拷贝,就可能出现两个 std::lock_guard 实例同时析构时重复解锁同一把锁,引发未定义行为(比如 std::mutex 的 unlock() 被调两次)。C++ 标准直接禁用了拷贝构造和拷贝赋值,只保留移动——但注意:std::lock_guard 实际上也删除了移动构造函数(C++17 起),它根本不可移动,只能栈上构造、自动析构。
- 错误写法:
auto lg = std::lock_guard<:mutex>(m);</:mutex>→ 触发拷贝,编译失败 - 正确写法:必须直接声明,不加
=:std::lock_guard<:mutex> lg(m);</:mutex> - 别名简化常用:
using lock_t = std::lock_guard<:mutex>;</:mutex>,然后写lock_t lg(m);
std::lock_guard 和 std::unique_lock 选哪个?
std::lock_guard 是轻量级 RAII 封装,只做「构造即加锁、析构即解锁」这一件事;std::unique_lock 更灵活,支持延迟锁定、手动解锁、条件变量配合、可转移所有权。如果你只需要保护一段临界区,且不需要中途解锁或等待,std::lock_guard 更合适——它更小、更快、意图更明确。
- 用
std::lock_guard:临界区短、逻辑线性、不涉及std::condition_variable - 必须用
std::unique_lock:要调cv.wait(lk, pred)、需要lk.unlock()提前释放锁、或想把锁传给别的函数 - 性能差异:
std::lock_guard通常比std::unique_lock少几个字节、无状态成员,构造/析构开销更低
std::lock_guard 构造失败会怎样?
std::lock_guard 构造函数会立即调用底层 mutex 的 lock()。如果该调用抛异常(例如 std::system_error,常见于互斥量已被销毁、或递归锁在非递归 mutex 上重入),std::lock_guard 构造失败,对象不会被创建,也就没有析构风险——但你的代码必须处理这个异常,否则会栈展开到上层。
- 典型错误场景:
std::mutex对象生命周期短于std::lock_guard(比如局部std::mutex被提前销毁) - 安全做法:确保 mutex 的生存期覆盖所有可能的
std::lock_guard使用点;必要时用try_lock+std::defer_lock配合std::unique_lock - 不要假设
std::lock_guard(m)总是成功——尤其在线程池或长期运行服务中,资源争用或意外销毁都可能发生
多个锁怎么避免死锁?用 std::lock 一次性获取
当需要同时锁多个 mutex(比如操作两个共享容器),手写顺序加锁容易因不同线程加锁顺序不一致导致死锁。std::lock 函数能按内部约定顺序原子地锁定多个可锁定对象,配合 std::lock_guard 的延期构造(使用 std::defer_lock tag)即可安全实现。
立即学习“C++免费学习笔记(深入)”;
std::mutex m1, m2; std::lock(m1, m2); // 原子锁定两个 std::lock_guard<std::mutex> lg1(m1, std::defer_lock); std::lock_guard<std::mutex> lg2(m2, std::defer_lock); // 此时 m1/m2 已锁,lg1/lg2 析构时会各自 unlock,不会重复操作
- 关键点:必须用
std::defer_lock,否则std::lock_guard构造时会再次尝试 lock,导致 double-lock 错误 - 更推荐封装为作用域块:
std::scoped_lock lk(m1, m2);(C++17),它本质就是带std::lock的std::lock_guard升级版 - 注意:所有参与
std::lock的 mutex 类型必须一致,且都支持lock()/unlock()接口
std::lock_guard 连报错的机会都没有,直接 UB。


















