c++ - std::condition_variable::notify_one:如果某些线程有假谓词,它会唤醒多个线程吗?

标签 c++ c++11

我有一个用于读取/写入的环形缓冲区。我跟踪环形缓冲区中的条目数,并且不允许覆盖尚未读取的条目。我使用 std::condition_variable wait() 和 notify_one() 来同步读者和作者。基本上,读者的条件是条目数 > 0。作者的条件是条目数 < 容量。

这一切似乎都有效,但有一件事我不明白。当读取器或写入器调用 notify_one() 时,它不会导致上下文切换。我已经阅读并理解它是这样工作的。但是,在写入器写入条目以填充缓冲区的情况下,写入器调用 notify_one() 并继续写入另一个,在这种情况下,其谓词在其 wait() 中失败。在这种情况下,我看到另一个 writer() 可能会醒来并且它的谓词也会失败。然后一个读者会醒来并且它的谓词成功并且它可以开始阅读。

我不明白为什么在一个 notify_one() 上多个线程被解除阻塞。带有失败谓词的 wait() 不会吃掉通知吗?我找不到任何说明情况如此的内容。

我可以调用 notify_all() 只是为了确定,但它似乎与 notify_one() 一起工作。

这是代码。

#include <iostream>
#include <stdint.h>

#include <boost/circular_buffer.hpp>
#include <condition_variable>
#include <thread>


// ring buffer with protection for overwrites 
template <typename T>
class ring_buffer {

  public:

    ring_buffer(size_t size) {
        cb.set_capacity(size);
    }

    void read(T& entry) {
        {
            std::unique_lock<std::mutex> lk(cv_mutex);
            cv.wait(lk, [this] {
                    std::cout << "read woke up, test=" << (cb.size() > 0) << std::endl; 
                    return 0 < cb.size();});
            auto iter = cb.begin();
            entry = *iter;
            cb.pop_front(); 
            std::cout << "Read notify_one" << std::endl;
        }
        cv.notify_one();
    } 

    void write(const T& entry) {
        {
            std::unique_lock<std::mutex> lk(cv_mutex);
            //std::cout << "Write wait" << std::endl;
            cv.wait(lk, [this] {
                    std::cout << "write woke up, test=" << (cb.size() < cb.capacity()) << std::endl; 
                    return cb.size() < cb.capacity();});
            cb.push_back(entry);
            std::cout << "Write notify_one" << std::endl;
        }
        cv.notify_one();
    }

    size_t get_number_entries() {
        std::unique_lock<std::mutex> lk(cv_mutex);
        return cb.size();
    }

  private:

    boost::circular_buffer<T> cb;
    std::condition_variable cv;
    std::mutex cv_mutex;
};

void write_loop(ring_buffer<int> *buffer) {

    for (int i = 0; i < 100000; ++i) {
        buffer->write(i);
    }
}

void read_loop(ring_buffer<int> *buffer) {

    for (int i = 0; i < 50000; ++i) {
        int val;
        buffer->read(val);
    }

}

int main() {

    ring_buffer<int> buffer(1000); 
    std::thread writer(write_loop, &buffer);
    std::thread reader(read_loop, &buffer);
    std::thread reader2(read_loop, &buffer);

    writer.join();
    reader.join();
    reader2.join();

    return 0;
}

我在输出中看到以下内容,其中多个线程被唤醒,因为谓词为假。

read woke up, test=0 
read woke up, test=0 
write woke up, test=1 

最佳答案

当您的每个读取线程检查它是否应该等待,或者是否已经满足条件时,您将看到条件的初始测试。

来自 here , wait() 的这个重载等同于

while (!pred()) {
    wait(lock);
}

所以 wait() 仅在条件为 true 时调用,但必须先检查条件。

read woke up, test=0  // tests condition on reader1 thread, false, wait is called
read woke up, test=0  // tests condition on reader2 thread, false, wait is called
write woke up, test=1 // tests condition on writer thread, true, wait is not called

This写入 2 个值的位置可能会很明显,每个读者只会读取一个值。

关于c++ - std::condition_variable::notify_one:如果某些线程有假谓词,它会唤醒多个线程吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58674475/

相关文章:

c++ - 为什么用空函数运行 std::thread 会消耗大量内存

c++ - 增加两个功能

c++ - 除了运算符优先级之外,额外的括号何时会产生影响?

c++ - C++11 类型推导中的右值引用

c++ - 为什么 tuple_size 是特征而不是成员

c++ - 查找数字中特定的重复数字

c++ - std::mutex 初始化异常

c++ - 将多种(不同)类型的参数传递给模板函数

c++ - 为什么无论类型如何,类中的引用变量总是占用 4 个字节? (在 32 位系统上)

c++ - 如何让 NetBeans 在执行语法检查时使用 C++14?