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_locklock_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

总结

  1. cv.wait(lk, pred) 内部会原子地解锁→阻塞→被唤醒后加锁,这是 lock_guard 做不到的,因此必须用 unique_lock
  2. 消费完数据后 lk.unlock(),让生产者线程尽快拿到锁继续生产,降低临界区长度。
  3. done 标志 + cv 保证消费者在生产结束后不会无限等待。

疑问:cv.wait(lk, []{ return !q.empty() || done; });好像没有什么用?

当最后一次 cv.notify_one();的时候,consumer会立刻执行,因为队列这个时候不是为空,原本consumer执行完后,要继续“睡”,但是因为done 赋值为true了以后,导致consumer又被唤醒,然后告诉consumer“下班了,后面没有了”

posted @ 2025-11-04 09:56  Tlink  阅读(6)  评论(0)    收藏  举报