条件变量 cv:用于线程间的等待与通知。std::unique_lock(主线程用)。std::lock_guard(子线程用)

下面我用更简单直观的方式,逐步带你理解清楚:

🔑 整个流程重新用通俗的话再讲一遍:

先明确几个关键角色:

  • 互斥量 (mutex):确保同一时间只有一个线程访问共享数据(这里就是变量ready)。
  • 条件变量 cv:用于线程间的等待与通知。
  • 锁管理器
    • std::unique_lock(主线程用):灵活,可以手动解锁或重新上锁。
    • std::lock_guard(子线程用):简单,自动加锁与解锁。

🚩 代码流程通俗讲解:

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

【主线程流程】:

主线程代码片段:

std::unique_lock<std::mutex> lock(mtx);
std::cout << "主线程等待子线程的通知...\n";
cv.wait(lock, []{ return ready; }); // 这里是关键

主线程执行过程

  1. std::unique_lock<std::mutex> lock(mtx);

    • 主线程此时给mtx 上了锁
    • 如果锁成功(没其他线程占用),继续向下执行。
  2. 主线程进入cv.wait(lock, []{ return ready; });

    • 先检查ready
      • 如果ready == true,不等待,直接往下走。
      • 如果ready == false,主线程必须等待子线程通知。

    这时候,主线程暂时无法继续,必须等待。

  3. 主线程自动释放锁

    • 为了让子线程能顺利更新ready的值,主线程在等待时自动解锁(释放mtx)。
    • 主线程进入阻塞状态(睡眠)。

子线程的执行过程

{
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
}
cv.notify_one();

子线程过程:

  1. 加锁

    • 执行到{}内部时,lock_guard自动调用mtx.lock()
    • 若主线程已经释放了锁,此时子线程顺利拿到锁,进入临界区。
  2. 修改变量

    • readyfalse改为true
  3. 自动解锁

    • 执行完{}代码块,子线程lock_guard自动解锁。
    • 此时mtx.unlock()自动调用。
  4. 通知主线程

    • cv.notify_one()告诉主线程:“你可以醒来了”。

🔑 这里为什么主线程能自动醒来?

因为主线程此刻正处在:

cv.wait(lock, []{ return ready; });

主线程醒来后自动重新加锁,相当于做下面这些事:

  • 收到子线程的notify_one(),主线程醒来。
  • wait()内部自动重新执行:
    lock.lock(); // 重新拿到锁
    if (ready == true) {
        return; // 条件满足,主线程继续
    } else {
        lock.unlock(); // 若条件未满足,再次等待(这里的代码帮你自动完成)
        cv.wait(lock);
    }
    

因此这个过程实际是循环的,直到条件满足。

---

### 🚩 **用生活中的例子解释一下**:

假设:

- **主线程**是一个正在等待的人,等待某个信号才继续做事。
- **子线程**则是负责给主线程发送通知的角色。

> 想象主线程在等一个快递包裹(信号)。

- 主线程先打开门锁(`mtx`),确认没有快递(`ready == false`),于是关门,但门不上锁,坐在门口等待快递(`cv.wait()`会自动解锁,坐着等待通知)。
- 快递员(子线程)拿到了快递,想要送进门,发现门没锁(因为主线程暂时解锁了),于是进来放下快递(将`ready = true`)。
- 快递放下后,快递员出门顺手带门(自动释放锁`lock_guard`自动解锁),通知主线程:“包裹到了,你可以看了!”(`notify_one()`)。

- 主线程听到通知后,站起来重新开门确认(`wait`自动重新加锁,确认`ready == true`),包裹确实到了,于是取出包裹,继续做后面的事情。

---

## 🔑 重要步骤明确总结:

| 时刻  | 动作 | 主线程状态 | 子线程状态 | mtx锁的状态 |
| ----- | ------ | ----------- |
| 起始 | 主线程加锁,检查条件 | 🔒 已上锁(主线程)|
| 主线程等待时 | 条件不满足,自动解锁 | 🔓 已解锁|
| 子线程启动时 | 拿到锁,修改变量 | 🔒 已上锁(子线程)|
| 子线程结束时 | 修改完毕,自动解锁 | 🔓 已解锁|
| 子线程通知时 | 主线程醒来重新上锁,条件满足继续 | 🔒 已上锁(主线程)|

---

## 🌟 **整体再一句话总结**:

- **主线程先加锁**,发现条件不满足,**自动释放锁并进入等待**;
- 子线程得到锁,执行完任务后,设置条件为满足,**自动释放锁后通知主线程**;
- 主线程收到通知后,**自动重新加锁**检查条件,满足后继续执行。

---

## 💡 注意的核心点:

- `std::unique_lock` 可以自动释放、重新获取锁(灵活)。
- `cv.wait()` 和 `cv.notify_one()`是成对使用的:
  - `wait()`自动释放锁后等待;
  - 收到通知后,自动重新获得锁并检查条件。

这个过程你无需手动进行解锁与加锁,C++条件变量的API会自动帮你完成。

---

## 🚩 总结一句话:

- **`unique_lock`自动帮主线程释放锁(进入等待)和重新加锁(收到通知)**;
- **`lock_guard`自动帮子线程加锁(访问资源)和自动释放锁(任务完成)**;
- 主线程与子线程交替拿到锁,保证线程安全。

这就是你代码里锁定和解锁的详细流程。
### `std::unique_lock` 的用法常见错误 `std::unique_lock` 是 C++ 标准库中用于管理互斥锁的通用机制之一,相较于 `std::lock_guard`,它提供了更灵活的锁定策略,包括延迟锁定、尝试锁定、超时锁定以及手动解锁等功能。`std::unique_lock` 通常用于需要更精细控制锁生命周期的场景,例如条件变量等待操作。 #### 基本用法 `std::unique_lock` 的基本构造方式如下: ```cpp std::mutex mtx; std::unique_lock<std::mutex> lock(mtx); ``` 上述代码构造了一个 `std::unique_lock` 实例,并在构造时锁定关联的互斥量 `mtx`。若希望延迟锁定,可使用如下方式: ```cpp std::unique_lock<std::mutex> lock(mtx, std::defer_lock); lock.lock(); // 手动加锁 ``` 这种方式适用于需要在特定条件下加锁的场景。 #### 条件变量结合使用 `std::unique_lock` 常 `std::condition_variable` 配合使用,以实现线程间的同步。例如: ```cpp std::mutex mtx; std::condition_variable cv; bool ready = false; void wait_for_ready() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return ready; }); // 继续执行 } void set_ready() { std::lock_guard<std::mutex> lock(mtx); ready = true; cv.notify_all(); } ``` 该模式广泛应用于线程任务调度、生产者-消费者模型等并发场景[^1]。 #### 常见错误 1. **未指定模板参数** 若直接声明 `std::unique_lock` 而未指定模板参数,例如: ```cpp std::unique_lock lock; // 错误:缺少模板参数 ``` 将导致编译错误 `missing template arguments before 'lock'`。正确用法应为: ```cpp std::unique_lock<std::mutex> lock(mtx); ``` 2. **重复解锁** `std::unique_lock` 在析构时会自动解锁其持有的互斥量。若手动调用 `.unlock()` 后再次析构,可能导致未定义行为。 3. **跨函数传递未释放锁** 若将 `std::unique_lock` 实例作为参数传递给其他函数而未释放所有权,可能导致锁的持有时间超出预期,影响并发性能。 4. **未正确处理异常** 若在加锁后抛出异常,但未使用 `std::unique_lock` 管理锁的生命周期,可能造成死锁。 #### 使用 `std::shared_mutex` 的写锁 `std::unique_lock` 也可用于 `std::shared_mutex` 的写锁操作,例如: ```cpp std::shared_mutex rw_mutex; void writer() { std::unique_lock<std::shared_mutex> lock(rw_mutex); // 写操作 } ``` 该方式确保在写操作期间没有其他读线程或写线程访问资源。 #### 总结 `std::unique_lock` 提供了比 `std::lock_guard` 更灵活的锁管理机制,适用于需要手动控制加锁、解锁时机的并发场景。合理使用该机制可提升程序的并发性能安全性,但需注意模板参数缺失、重复解锁、异常处理等常见问题。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值