V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
V2WT
V2EX  ›  问与答

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

  •  
  •   V2WT · 2021-01-26 19:57:30 +08:00 · 596 次点击
    这是一个创建于 1407 天前的主题,其中的信息可能已经有所发展或是发生改变。

    首先根据: 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的信号到底去哪里了呢?

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

    5 条回复    2021-01-27 12:00:37 +08:00
    chuckzhou
        1
    chuckzhou  
       2021-01-26 20:53:30 +08:00
    notify_one 会检查是否有 waiter,如果没有,啥也不干就退出了。
    V2WT
        2
    V2WT  
    OP
       2021-01-26 21:00:34 +08:00
    @chuckzhou 假设现在有一个 Thread t3 = t2. 这样一次 notify_one(). 因为修改了标志位置。实际上 t2 t3 这两个线程都会执行。那这个 condition_variable 还有什么用呢?
    V2WT
        3
    V2WT  
    OP
       2021-01-26 21:10:12 +08:00
    @chuckzhou 啊这是我理解有问题了。多个同时等的时候,应该等到了要把 flag 取反的。 不好意思。。
    chuckzhou
        4
    chuckzhou  
       2021-01-26 21:19:56 +08:00
    @V2WT 如果是你说的这种情况,predicate 就不应该这么写。
    一个常见的场景就是生产者和消费者。
    一个线程生产了一个数据,比如把数据放到一个 list 里面,为了提高性能,可能有很多消费者,但是只有一个数据,如果你的 predicate 是 dataReady,那确实会导致消费者都运行,这显然是不对。
    所以你的 predicate 要写成 !list.empty() 这样,第一个线程把数据取走了之后,第二个线程就会继续等待了。
    V2WT
        5
    V2WT  
    OP
       2021-01-27 12:00:37 +08:00
    @chuckzhou 是的。后来想想明白了。感谢答复。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1248 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 23:25 · PVG 07:25 · LAX 15:25 · JFK 18:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.