C++中的lock
区别和联系
lock_guard 是轻量级保安,只管进门锁门;unique_lock 是万能管家,还能临时开门、换房、陪条件变量聊天。
1. 核心差异(表格速查)
| 特性 | lock_guard<Mutex> |
unique_lock<Mutex> |
|---|---|---|
| 自动加/解锁 | ✅ | ✅ |
手动中途 unlock() 再 lock() |
❌ | ✅ |
延迟加锁 (std::defer_lock) |
❌ | ✅ |
与 condition_variable 合作 |
❌ | ✅(必需) |
所有权转移 (std::move) |
❌ | ✅ |
| 对象大小 / 开销 | 最小(≈1 指针) | 略大(多状态标志) |
2. 典型场景对照
std::mutex m;
// 1. 简单临界区——lock_guard 足够
void simple() {
std::lock_guard<std::mutex> lk(m); // 构造即锁,析构即解
/* 临界区 */
}
// 2. 条件变量——必须用 unique_lock
void waiting_thread() {
std::unique_lock<std::mutex> lk(m); // 可以晚点锁、中途解
cv.wait(lk, []{ return ready; }); // 内部会解锁→等待→再锁
}
// 3. 提前解锁以减少粒度
void reduce_lock_time() {
std::unique_lock<std::mutex> lk(m);
/* 必须受保护的操作 */
lk.unlock(); // 提前解锁,让其他线程尽早进入
/* 其余耗时但无需锁的工作 */
} // 若忘解,这里仍会自动解
3. 联系与最佳实践
- 底层资源相同:都仅对一把
mutex做引用,不拥有它;生命周期内保证 mutex 被正确地unlock()。 - 可混用:同一线程对同一 mutex 先
unique_lock后lock_guard是安全的(前提是自己已持锁)。 - 性能敏感且逻辑简单 → 优先
lock_guard;需要条件变量、延迟锁、手动解锁、所有权转移 → 用unique_lock。 - 现代 C++ 更推荐 作用域锁:
std::scoped_lock(C++17)可一次性锁多个 mutex,避免死锁,是lock_guard的多锁升级版。
4. 小结
简单锁事 guard 办;
条件等待unique 管;
中途开关需灵活unique 全能干.
unique_lock 在condition_variable中的典型应用
一个生产者线程往队列里放数据,一个消费者线程用条件变量等待数据;std::unique_lock 全程配合 cv.wait(),实现零 CPU 空转的阻塞式消费。
(直接 g++ -std=c++17 file.cpp -pthread 就能跑)
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
std::queue<int> q; // 共享缓冲区
std::mutex m; // 保护 q
std::condition_variable cv; // 通知“队列非空”
bool done = false; // 生产结束标记
/* ========== 生产者 ========== */
void producer()
{
for (int i = 1; i <= 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 模拟耗时
{
std::lock_guard<std::mutex> lk(m); // 简单临界区,用 guard 即可
q.push(i);
std::cout << "[producer] push " << i << '\n';
}
cv.notify_one(); // 通知消费者
}
// 生产完毕
{
std::lock_guard<std::mutex> lk(m);
done = true;
}
cv.notify_one(); // 最后一次唤醒
}
/* ========== 消费者 ========== */
void consumer()
{
while (true) {
std::unique_lock<std::mutex> lk(m); // 1. 先加锁
// 2. 等待条件成立(队列非空 或 生产结束)
cv.wait(lk, []{ return !q.empty() || done; });
// 3. 被唤醒后仍持有锁,可以安全访问队列
if (!q.empty()) {
int val = q.front();
q.pop();
std::cout << "[consumer] pop " << val << '\n';
lk.unlock(); // 4. 提前解锁,减少锁粒度
/* 模拟耗时处理 */
std::this_thread::sleep_for(std::chrono::milliseconds(300));
} else if (done) {
std::cout << "[consumer] finished\n";
break; // 队列空且生产结束,退出
}
}
}
/* ========== main ========== */
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
运行示例输出
[producer] push 1
[consumer] pop 1
[producer] push 2
[consumer] pop 2
[producer] push 3
[consumer] pop 3
[producer] push 4
[consumer] pop 4
[producer] push 5
[consumer] pop 5
[consumer] finished
总结
cv.wait(lk, pred)内部会原子地解锁→阻塞→被唤醒后加锁,这是lock_guard做不到的,因此必须用unique_lock。- 消费完数据后
lk.unlock(),让生产者线程尽快拿到锁继续生产,降低临界区长度。 done标志 +cv保证消费者在生产结束后不会无限等待。
疑问:cv.wait(lk, []{ return !q.empty() || done; });好像没有什么用?
当最后一次 cv.notify_one();的时候,consumer会立刻执行,因为队列这个时候不是为空,原本consumer执行完后,要继续“睡”,但是因为done 赋值为true了以后,导致consumer又被唤醒,然后告诉consumer“下班了,后面没有了”

浙公网安备 33010602011771号