Использование Promise и Future для предоставления непрерывных периодических уведомлений от одного потока к другому

#c #c 11 #promise #future #condition-variable

#c #c 11 #обещание #будущее #условие-переменная

Вопрос:

У меня есть два потока. Один поток действует как поток таймера, который через регулярные промежутки времени должен отправлять уведомления другому потоку. Я планирую использовать C Promise и Future для этой цели вместо переменных условий C .

У меня есть следующие ограничения / условия :-

  1. Хотелось бы избежать блокировки мьютекса (поэтому решил не использовать C std::condition_variable для этого, нужно использовать мьютекс)
  2. Потоку таймера (или уведомляющему) не нужно отправлять уведомление потребителю, если он еще не готов (т. Е. Все еще Действует на последнее уведомление)

Я решил использовать C Promise и Future и придумал этот фрагмент кода.

 // promiseFutureAtomic.cpp

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <thread>
#include <iostream>       // std::cout, std::endl
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include <future>

std::promise<void> aPromisingFuture;
std::atomic<bool> readyForNotification{false};
std::atomic<bool> stop_consumer{false};

void waitingForWork(){
    while (!stop_consumer)
    {
        std::cout << "Waiting " << std::endl;
        
        std::promise<void> newPromise;
        aPromisingFuture = std::move(newPromise);
        auto aFuture = aPromisingFuture.get_future();
        readyForNotification = true;
        aFuture.wait();
        std::cout << "Running " << std::endl;
        // Do useful work but no critical section.
    }
}

void setDataReady(){
    int i = 0;
    while (i   < 10)
    {
        std::this_thread::sleep_for (std::chrono::seconds(1));
        if (readyForNotification)
        {
            readyForNotification = false;
            std::cout << "Data prepared" << std::endl;
            aPromisingFuture.set_value();
        }
    }
    stop_consumer = true;
}

int main(){
    
  std::cout << std::endl;

  std::thread t1(waitingForWork);
  std::thread t2(setDataReady);

  t1.join();
  t2.join();
  
  std::cout << std::endl;
  
}
  

Мой вопрос :-

  1. Как производительность этого подхода будет сравниваться с предстоящим C 20 std::atomic_wait / std::atomic_notify ?
  2. std::future<T>::wait() Страдает ли от ложных пробуждений (например, C std::condition_variable::wait )?
  3. Есть ли какие-либо недостатки этого подхода по сравнению с обычным подходом std::condition_variable::wait ?

Комментарии:

1. @EOF — std::this_thread::sleep_for там нет, чтобы избежать мьютекса. Он предназначен только для имитации таймера (если вы прочитали вопрос, в нем говорится, что один поток действует как таймер, детали таймера в этом контексте не имеют значения, и режим сна просто имитирует это.) С отключением режима сна, чем это std::atomic<bool> хуже, чем мьютекс?

Ответ №1:

Условие гонки в примере кода

В приведенном выше примере есть условие гонки. Если setDataReady() устанавливает для stop_consumer значение true после того, как функция waitingForWork() проверила условие !stop_consumer, но до того, как она вызвала функцию aFuture.wait(), то функция waitingForWork() будет ждать завершения.

Это можно преодолеть с помощью std::future::wait_for() , который может по таймауту, позволяя повторную проверку stop_consumer . Я ценю, что в простом примере кода вы можете не захотеть загромождать его дополнительным кодом для перепроверки.

Как Windows, так и Linux поддерживают ожидание нескольких дескрипторов с помощью WaitForMultipleObjects() и poll() / epoll() . Используя эти собственные методы, уведомление потока может выполняться с ожиданием, которое не требует тайм-аута. Вместо этого код может ожидать уведомления либо обычного дескриптора, либо дескриптора завершения работы. Я не сталкивался с поддержкой стандартной библиотеки для этого.

Ложные пробуждения

В пункте 39 Effective Modern C Скотт Мейерс обсуждает то, что он называет void futures . Это уведомления, подобные приведенным выше, хотя и с одним выстрелом. Он перечисляет отсутствие ложных пробуждений как одну из причин предпочесть future / promise переменным условий.

Другие преимущества future / promise

Мейерс перечисляет несколько других причин, по которым он предпочитает future / promise вместо condition_variable:

  • Запах кода мьютекса, необходимого для сопровождения condition_variable .
  • Необходимость избегать уведомления переменной условия до того, как потребляющий поток ожидает condition_variable .

Производительность future / promise по сравнению с std::atomic_notify

Я не смотрел, одна из причин, по которой я подозреваю, что производительность больше зависит от переключения контекста и планирования потоков, чем от чистого уведомления. Даже если уведомление было значительным, это могло зависеть от реализации.

Другие мысли

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