C++中lock_guard和unique_lock的区别?(灵活度不同的互斥锁封装)

必须用 uni

que_lock 而不能用 lock_guard 的情况包括:延迟加锁、手动控制加解锁时机、配合条件变量 wait()、尝试非阻塞加锁(try_lock)、分支逻辑决定是否加锁,以及需要移动语义转移锁所有权。

什么时候必须用 unique_lock 而不能用 lock_guard

当需要延迟加锁、手动控制加锁/解锁时机、或配合条件变量使用时,lock_guard 完全无法胜任——它在构造时强制加锁、析构时强制解锁,没有中间状态。unique_lock 支持默认构造(不持有锁)、lock()/unlock() 显式调用、以及 try_lock() 等操作。

  • 条件变量的 wait() 必须传入 unique_lock,因为 wait() 内部要先临时释放锁再挂起线程,唤醒后重新加锁——lock_guard 不支持临时解锁
  • 想尝试加锁但不想阻塞?unique_lock 提供 try_lock()lock_guard 没有对应能力
  • 函数内部分支逻辑才决定是否加锁?只能用 unique_lock 默认构造,后续按需 lock()

unique_lock 的移动语义和所有权转移是干什么用的

unique_lock 可被移动(move),意味着能将锁的所有权从一个对象转移到另一个对象,比如作为函数返回值、存入容器、或跨作用域传递。而 lock_guard 是不可复制也不可移动的,生命周期严格绑定在当前作用域。

  • 常见场景:写一个封装了锁逻辑的 RAII 类,内部持有 unique_lock 成员,靠移动语义把锁“带出去”
  • 错误用法:lock_guard 尝试 std::move 或赋值会编译失败,报错类似 use of deleted function
  • 注意:移动后原 unique_lock 变为空状态(owns_lock() == false),不能再 unlock()

性能和内存开销差异真的存在吗

是的。lock_guard 是零开销抽象:它只保存一个 mutex*,无额外成员,大小通常等于指针宽度;unique_lock 多一个布尔标记位(记录是否持有锁),并需支持延迟构造与移动,编译器优化后仍略大一点,且某些操作(如 try_lock())有轻微运行时分支判断。

  • 纯保护临界区且无需灵活控制?优先选 lock_guard —— 更轻量、意图更明确
  • 不需要移动、也不需要延迟/手动解锁?用 lock_guard 反而更安全:杜绝误操作导致的死锁或未加锁访问
  • 现代编译器对两者的内联和优化都很强,性能差距在绝大多数场景下可忽略,但语义清晰度差异显著

一个典型误用:在 if 分支里用 lock_guard 却以为能控制加锁范围

下面这段代码看似“只在条件成立时加锁”,实际并非如此:

if (condition) {
    std::lock_guard guard(mtx);
    // 临界区
}

它确实只在 condition 为真时构造 lock_guard,但问题在于:这个 guard 的作用域仍是该 if 块,不是整个函数。很多人真正想要的是“根据条件决定是否进入临界区”,但又希望锁的生命周期跨越多个语句甚至函数调用——这时必须用 unique_lock 手动管理:

std::unique_lock guard;
if (condition) {
    guard = std::unique_lock(mtx); // 延迟加锁
}
// 后续代码可依赖 guard.owns_lock() 判断是否已加锁

否则,仅靠 lock_guard 的作用域机制,无法表达“加锁 → 做事 → 条件性解锁”这类非对称控制流。

最常被忽略的一点:unique_lock 的灵活性是一把双刃剑——它允许你写出正确但难维护的锁逻辑,也容易写出忘记 unlock() 或重复 unlock() 的 bug;而 lock_guard 的刚性恰恰是它的健壮性来源。选哪个,本质是在「控制力」和「约束力」之间做取舍。