diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index 66b7f2a5..1a75412a 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -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; @@ -37,7 +37,7 @@ void wait_for_flag(){ } ``` -第二种方法就是加个延时,这种实现进步了很多,减少浪费的执行时间,但很难确定正确的休眠时间。这会影响到程序的行为,在需要快速响应的程序中就意味着丢帧或错过了一个时间片。 +第二种方法就是加个延时,这种实现进步了很多,减少浪费的执行时间,但很难确定正确的休眠时间。这会影响到程序的行为,在需要快速响应的程序中就意味着丢帧或错过了一个时间片。循环中,休眠②前函数对互斥量解锁①,再休眠结束后再对互斥量上锁,让另外的线程有机会获取锁并设置标识(因为修改函数和等待函数共用一个互斥量)。 ```cpp void wait_for_flag(){ @@ -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` 函数中: @@ -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& lock); // 1 + +template +void wait(std::unique_lock& lock, Predicate pred); // 2 +``` + +②等价于: + +```cpp +while (!pred()) + wait(lock); +``` + +这可以避免““[虚假唤醒(spurious wakeup)](https://en.wikipedia.org/wiki/Spurious_wakeup)”。 + +> 条件变量虚假唤醒是指在使用条件变量进行线程同步时,有时候线程可能会在没有收到通知的情况下被唤醒。问题取决于程序和系统的具体实现。解决方法很简单,在循环中等待并判断条件可一并解决。使用 C++ 标准库则没有这个烦恼了。 + +## 线程安全的队列 +