一、引言
本文主要讨论 lock_guard、unique_lock的区别,以及条件变量使用wait函数时为什么不能使用lock_guard的原因
二、lock_guard、unique_lock的核心区别
cpp
std::unique_lock<std::mutex> lock(q_mutex); // unique_lock可以解锁
// std::lock_guard<std::mutex> lock(q_mutex); //会发生报错,qt编译不通过,因为lock_guard只能锁定一次,不能解锁,知道析构,
2.1 lock_guard
lock_guard的对象是构造完毕后就上锁完毕获取到锁了,直到析构时才会解锁。(这里如果读起来很拗口难懂,可以先看看我另外一篇博客中关于"上锁、解锁、获取锁、释放锁的几个概念区别")在这期间,lock_guard是不能解锁的
2.2 unique_lock
unique_lock可以随时随地地unlock()与lock(),这样也就更加地灵活。
这里附加上gpt总结完整表格
| 特性 | std::lock_guard |
std::unique_lock |
|---|---|---|
| 可解锁性 | ❌ 不可手动解锁(构造即锁,析构即解) | ✅ 可随时 unlock()/lock() |
| 灵活性 | 轻量级,单一职责 | 功能强大,状态可管理 |
| 使用场景 | 简单临界区保护 | 条件变量、延迟锁定、手动解锁 |
| 性能开销 | 零额外开销(纯 RAII) | 略高(维护锁定状态,多1-2字节) |
能否与 wait() 配合 |
绝对不能 | 必须使用 |
| 构造函数 | lock_guard(mutex) |
unique_lock(mutex) 或 unique_lock |
三、为什么 condition_variable::wait()必须用unique_lock?
wait()函数中会走三个步骤:
调用 wait() 时,内部执行不可分割的序列:
- unlock(mutex) :释放锁,让其他线程能修改共享状态
- block(wait_queue) :线程进入等待队列,挂起
- lock(mutex) :被唤醒后,重新获取锁,才返回继续执行
lock_guard的致命缺陷也就是在这里,使用wait()函数核心目的就是阻塞某个线程等待某个条件达到后再让这个线程运行下去,但是lock_guard只有析构时才可以解锁。如果wait()函数中lock_guard一直不解锁,那么其他线程就无法获取这个锁,也就是说其他 线程会被一直阻塞住。
四、应用时机与场景
下面是一些应用场景的ai总结
场景 A:简单临界区 → lock_guard
cpp
void simple_update() {
std::lock_guard<std::mutex> lock(mtx);
shared_data = 42;
} // 自动解锁
场景 B:条件变量 → unique_lock
cpp
void conditional_wait() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]{ return ready; }); // 需要解锁/重新锁
// 获取数据
}
场景 C:延迟锁定 → unique_lock
cpp
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 暂不锁定
// ... 一些不需要锁的操作 ...
lock.lock(); // 现在锁定
// ... 临界区 ...
五、黄金法则
- 默认用
lock_guard:除非有特殊需求 - 需要
wait()时用unique_lock:这是唯一强制场景 - 不要手动
mutex.lock()/unlock():易忘解锁导致死锁 wait()函数中返回true就放行 :Lambda 返回 true 停止等待