std::lock_guard是构造即加锁、析构即解锁的不可复制/移动的RAII锁,适用于简单临界区;std::unique_lock支持延迟锁定、手动解锁、条件变量和所有权转移,更灵活但稍重。

lock_guard 是个“一锤子买卖”,构造即加锁、析构即解锁
它没有移动或复制能力,生命周期完全绑定到作用域——只要定义了 std::lock_guard,就立刻尝试加锁;离开作用域时自动调用析构函数释放锁。适合最简单的临界区保护场景。
常见错误是试图把它当作可转移的对象来用:std::lock_guard 的拷贝构造和移动构造都被显式删除,所以不能放进容器、不能作为函数返回值、也不能赋值给另一个 lock_guard。
使用建议:
- 只在需要“进作用域就锁、出作用域就放”的简单同步时用
- 不要传参给它带
std::defer_lock——它不支持延迟锁定,这个标记只对std::unique_lock有效 - 如果临界区里可能抛异常,
lock_guard反而是更安全的选择(因为析构保证释放)
unique_lock 支持延迟锁定、手动解锁、条件变量配合和所有权转移
std::unique_lock 更像一个“可操控的锁句柄”。它允许你控制加锁时机(比如用 std::defer_lock 构造后稍后再调 lock()),也能在临界区中途主动 unlock(),再在必要时 lock() 回来——这对避免锁粒度过大很有用。
立即学习“C++免费学习笔记(深入)”;
典型使用场景包括:
- 与
std::condition_variable配合:必须用unique_lock,因为wait()要求能临时释放并重获锁 - 实现 try-lock 逻辑:
try_lock()返回bool,失败也不阻塞 - 把锁所有权移交给另一个作用域(通过移动语义),比如从工厂函数返回已加锁的
unique_lock
注意:它比 lock_guard 稍重(内部多一个布尔状态位),且默认构造时不关联任何互斥量,使用前必须显式绑定或移动赋值。
别混用 defer_lock 和 lock_guard,也别误以为 unique_lock 默认就加锁
这是面试高频踩坑点。写 std::unique_lock<:mutex> lk(mtx, std::defer_lock) 后,lk 是未锁定状态;而写 std::lock_guard<:mutex> lg(mtx) 则必然立即阻塞等待加锁成功(除非抛异常)。
另一个常见误操作是:在 unique_lock 已持有时,又对同一互斥量调用 mtx.lock() ——这会导致未定义行为(通常死锁或崩溃),因为 unique_lock 已接管该 mutex 的所有权。
参数差异要点:
-
std::defer_lock→ 构造时不加锁 -
std::try_to_lock→ 构造时非阻塞尝试加锁,失败则对象处于未锁定状态 -
std::adopt_lock→ 假设当前线程已持有该 mutex,直接接管(常用于先mtx.lock()再构造unique_lock)
性能与可读性权衡:多数情况优先用 lock_guard
除非你明确需要 unique_lock 提供的灵活性,否则默认选 lock_guard。它的语义更清晰、开销更低、且编译器更容易做优化(比如 NRVO 或栈分配消除)。
实际项目中,过度使用 unique_lock 容易带来两类问题:
- 忘记调
lock()就进入临界区,导致数据竞争(编译器不会报错) - 在复杂控制流中多次
lock()/unlock(),增加维护成本和出错概率
真正需要手动干预锁生命周期的地方其实不多,比如实现自定义同步原语、或封装跨多个 mutex 的锁顺序逻辑——这时候才值得引入 unique_lock 的额外复杂度。











