关于 C++ 11 中的 condition_variable. 有一处没想明白。

2021-01-26 19:57:30 +08:00
 V2WT

首先根据: The C++ core guideline 的第 42 条中提到:不要在没有条件的情况下等待( Don’t wait without a condition).主要是为了解决:

下面是范例代码:

#include <condition_variable>
#include <iostream>
#include <thread>

std::mutex mutex_;
std::condition_variable condVar; 
bool dataReady{false};

void waitingForWork(){
    std::cout << "Waiting " << std::endl;
    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck, []{ return dataReady; });   // (4)
    std::cout << "Running " << std::endl;
}

void setDataReady(){
    {
        std::lock_guard<std::mutex> lck(mutex_);
        dataReady = true;
    }
    std::cout << "Data prepared" << std::endl;
    condVar.notify_one();                        // (3)
}

int main(){
  std::cout << std::endl;
  std::thread t1(waitingForWork);               // (1)
  std::thread t2(setDataReady);                 // (2)
  t1.join();
  t2.join();
  std::cout << std::endl;
}

这里再贴一下搜到的资料里 condition_variable 的处理逻辑: wait (lck, pred); 其实等价于 while (!pred()) wait(lck);,它的运行机制如下:

  1. 线程获取 mutex 的锁,然后对 predicate 的结果进行检查: true:线程继续往下执行; false:condVar.wait() 解锁 mutex,然后线程进入等待(阻塞)状态。
  2. 假如 condVar 已经在等待状态,此时得到通知(不管发送线程发送,还是虚假唤醒): 线程进入非阻塞状态,然后重新获取 mutex 的锁。 线程检查 predicate 的结果: true:线程继续往下执行; false:condVar.wait() 解锁 mutex,然后线程进入等待(阻塞)状态。

这里我的问题是: 假设运行步骤是这样:

  1. t2 线程(setDataReady)先执行了。这个时候dataReady = true. 并且 notify_one了。
  2. t1 线程(waitingForWork)里 wait 的 predicate 为 true.直接往下执行了。并不会阻塞在 condVar.wait 的阶段(根据上面的运行机制是这样)。

所以这个时候相当于空白 notify_one了一次。实际上 t1 线程的运行只跟 dataReady有关,而跟 condition_variable 无关了。那么这次的notify_one的信号到底去哪里了呢?

不知道我有没有描述清楚问题。有大佬能解答一下吗?

546 次点击
所在节点    问与答
5 条回复
chuckzhou
2021-01-26 20:53:30 +08:00
notify_one 会检查是否有 waiter,如果没有,啥也不干就退出了。
V2WT
2021-01-26 21:00:34 +08:00
@chuckzhou 假设现在有一个 Thread t3 = t2. 这样一次 notify_one(). 因为修改了标志位置。实际上 t2 t3 这两个线程都会执行。那这个 condition_variable 还有什么用呢?
V2WT
2021-01-26 21:10:12 +08:00
@chuckzhou 啊这是我理解有问题了。多个同时等的时候,应该等到了要把 flag 取反的。 不好意思。。
chuckzhou
2021-01-26 21:19:56 +08:00
@V2WT 如果是你说的这种情况,predicate 就不应该这么写。
一个常见的场景就是生产者和消费者。
一个线程生产了一个数据,比如把数据放到一个 list 里面,为了提高性能,可能有很多消费者,但是只有一个数据,如果你的 predicate 是 dataReady,那确实会导致消费者都运行,这显然是不对。
所以你的 predicate 要写成 !list.empty() 这样,第一个线程把数据取走了之后,第二个线程就会继续等待了。
V2WT
2021-01-27 12:00:37 +08:00
@chuckzhou 是的。后来想想明白了。感谢答复。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/748665

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX