Skip to content

Commit

Permalink
完成第四章第一节的全部内容“等待事件或条件”,修改了之前的措辞
Browse files Browse the repository at this point in the history
  • Loading branch information
Mq-b committed Apr 24, 2024
1 parent ebae77f commit c0dcfe8
Showing 1 changed file with 30 additions and 5 deletions.
35 changes: 30 additions & 5 deletions md/04同步操作.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

## 等待事件或条件

假设你正在一辆夜间运行的地铁上,那么你在正确的站点下车呢
假设你正在一辆夜间运行的地铁上,那么你要如何在正确的站点下车呢

1. 一直不休息,每一站都能知道,这样就不会错过你要下车的站点,但是这会很疲惫。

2. 可以看一下时间,估算一下火车到达目的地的时间,然后设置一个稍早的闹钟,就休息。这个方法听起来还行,但是你可能被过早的叫醒,甚至估算错误,以及坐过站,又或者闹钟没电了,睡过站
2. 可以看一下时间,估算一下火车到达目的地的时间,然后设置一个稍早的闹钟,就休息。这个方法听起来还行,但是你可能被过早的叫醒,甚至估算错误导致坐过站,又或者闹钟没电了睡过站

3. 事实上最简单的方式是,到站的时候有人或者其它东西能将你叫醒(比如手机的地图,到达设置的位置就提醒)。

这和线程有什么关系呢?其实第一种方法就是在说轮询
这和线程有什么关系呢?其实第一种方法就是在说”[忙等待](https://zh.wikipedia.org/wiki/%E5%BF%99%E7%A2%8C%E7%AD%89%E5%BE%85)(busy waiting)”也称“**自旋**

```cpp
bool flag = false;
Expand All @@ -37,7 +37,7 @@ void wait_for_flag(){
}
```

第二种方法就是加个延时,这种实现进步了很多,减少浪费的执行时间,但很难确定正确的休眠时间。这会影响到程序的行为,在需要快速响应的程序中就意味着丢帧或错过了一个时间片。
第二种方法就是加个延时,这种实现进步了很多,减少浪费的执行时间,但很难确定正确的休眠时间。这会影响到程序的行为,在需要快速响应的程序中就意味着丢帧或错过了一个时间片。循环中,休眠②前函数对互斥量解锁①,再休眠结束后再对互斥量上锁,让另外的线程有机会获取锁并设置标识(因为修改函数和等待函数共用一个互斥量)。

```cpp
void wait_for_flag(){
Expand Down Expand Up @@ -91,7 +91,7 @@ void simulateArrival() {

2. `cv.wait(lck, []{ return arrived; })`: 在等待期间,当前线程会**释放锁**(unlock)并**等待**条件变量 `arrived` 变为 true。`cv.wait` 方法会自动释放锁并将当前线程加入到条件变量的等待队列中,直到被唤醒。

3. 一旦条件满足,即 `arrived` 变为 true,并且当前线程被其它线程唤醒,那么当前线程会重新获取锁(lock),并执行后续的操作。
3. 一旦条件满足,即 `arrived` 变为 true,并且当前线程被其它线程**唤醒**(包括**虚假唤醒**,那么当前线程会重新获取锁(lock),并执行后续的操作。

`simulateArrival` 函数中:

Expand All @@ -102,3 +102,28 @@ void simulateArrival() {
3. `cv.notify_one()`: 通知等待在条件变量上的一个线程,唤醒其中一个等待线程。

这样,当 `simulateArrival` 函数执行后,`arrived` 被设置为 true,并且通过 `cv.notify_one()` 唤醒了等待在条件变量上的线程,从而使得 `waitForArrival` 函数中的等待结束,可以执行后续的操作,即输出提示信息。

---

条件变量的 `wait` 成员函数有两个版本,以上代码使用的就是第二个版本,传入了一个[*谓词*](https://zh.cppreference.com/w/cpp/named_req/Predicate)

```txt
void wait(std::unique_lock<std::mutex>& lock); // 1
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred); // 2
```

②等价于:

```cpp
while (!pred())
wait(lock);
```
这可以避免““[虚假唤醒(spurious wakeup)](https://en.wikipedia.org/wiki/Spurious_wakeup)”。
> 条件变量虚假唤醒是指在使用条件变量进行线程同步时,有时候线程可能会在没有收到通知的情况下被唤醒。问题取决于程序和系统的具体实现。解决方法很简单,在循环中等待并判断条件可一并解决。使用 C++ 标准库则没有这个烦恼了。
## 线程安全的队列

0 comments on commit c0dcfe8

Please sign in to comment.