std :: condition_variable :: notify_one: пробуждает ли он несколько потоков, если у некоторых есть ложный предикат? - PullRequest
0 голосов
/ 02 ноября 2019

У меня есть кольцевой буфер, который используется для чтения / записи. Я отслеживаю количество, если записи в кольцевом буфере, и не допускаю перезаписи записей, которые не были прочитаны. Я использую std :: condition_variable wait () и notify_one () для синхронизации читателей и писателей. В основном условие для читателя состоит в том, что количество записей> 0. Условие для читателей состоит в том, что количество записей <емкость. </p>

Кажется, все работает, но есть одна вещь, которую я не понимаю,Когда читатель или писатель вызывает 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;
}

В выводе я вижу следующее, когда пробуждаются несколько потоков, поскольку предикат имеет значение false.

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

1 Ответ

1 голос
/ 03 ноября 2019

Вы видите начальный тест условия, когда каждый из ваших потоков чтения проверяет, должно ли оно ждать или условие уже выполнено.

С здесь , эта перегрузка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

Это может сделать очевидным, где записаны 2 значения, и каждый читатель будет читать только одно значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...