4

I have two threads. One thread acts as a timer thread which at regular intervals of time needs to send a notification to another thread. I am planning to use C++ Promise and Future for this purpose instead of C++ condition variables.

I have the following constraints/conditions :-

  1. Would like to avoid locking on to mutex (hence decided not to use C++ std::condition_variable as for that, one has to use mutex)
  2. The timer (or notifying) thread need not send a notification to the consumer if it is not ready yet (i.e. still acting on the last notification)

I decided to use C++ Promise and Future and came up with this piece of code.

// 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;
  
}

My question is :-

  1. How would the performance of this approach compare with that the upcoming C++20 std::atomic_wait/std::atomic_notify ?
  2. Does std::future<T>::wait() suffer from spurious wakeups (like C++ std::condition_variable::wait) ?
  3. Are there any drawbacks of this approach over a regular approach using std::condition_variable::wait ?
bobbydev
  • 525
  • 3
  • 12
  • @EOF - the `std::this_thread::sleep_for` is not there to avoid a mutex. It is there just to simulate a timer (if you read the question, it says that one thread acts as a timer, details of the timer are irrelevant in this context and the sleep just simulates that.) With sleep out of the way, why is `std::atomic` worse than a mutex ? – bobbydev Oct 08 '20 at 09:31

1 Answers1

0

Race condition in the example code

In the above example there is a race condition. If setDataReady() sets stop_consumer to true after waitingForWork() has checked the !stop_consumer condition but before it has called aFuture.wait() then waitingForWork() will wait forover.

That can be overcome by using std::future::wait_for(), which can timeout, allowing a re-check of stop_consumer. I appreciate that in simple example code you may not want to clutter it with extra code for rechecking.

Both Windows and Linux support waiting on multiple handles with WaitForMultipleObjects() and poll()/epoll(). Using these native methods, thread notification can be performed with a wait that does not require a timeout. Instead, the code can wait for either the normal or the shutdown handle to be notified. I've not come across standard library support for that.

Spurious wakeups

In Item 39 of Effective Modern C++ Scott Meyers discusses what he calls void futures. These are notifications like above, although single shot. He lists the lack of spurious wakeups as one reason to prefer future/promise over condition variables.

Other advantages of future/promise

Meyers lists a couple of other reasons why he prefers future/promise over condition_variable:

  • Code smell of the mutex required to accompany a condition_variable.
  • The need to avoid the notification of the condition variable before the consuming thread is waiting for the condition_variable.

Performance of future/promise versus std::atomic_notify

I've not looked, one reason being I suspect that performance depends more on context switching and thread scheduling than on the pure notification. Even if the notification were significant then it could depend on the implementation.

Other thoughts

The above example is there as a simple example. In real usage, in something other than a small app, I would wrap the periodic notification code and avoid the use of globals.