Vlad Fedyaev
Vlad Fedyaev

Reputation: 115

std::condition_variable wait() and notify_one() synchronization

Preface: I've seen similar questions here, but not one of them seems to answer my question.

Is there a reliable way to make sure that wait() method in consumer thread is called before the first notify_one() call from the producer thread?

Even with unique_lock in the consumer thread, there is a possibility that the producer thread will run first, lock the mutex and call notify() before the consumer calls wait(), therefore, my app will be missing first notify() call.

EDIT: Thanks for all your answers, they did help me. My problem was with first wait-notify() within this consumer loop:

while (!timeToQuit) {
    gdcv.wait(gdcondlock);
    gdlock.lock();
    //spurious wakeup
    if (gdQueue.empty()) {
      gdlock.unlock();
      continue;
    }
    //some work here
    gdlock.unlock();
} 

I guess I'll have to write extra code for the first loop iteration.

EDIT2: This loop and second lock(unique_lock btw) are there because there are multiple producers and consumers accessing queue.

EDIT3: The correct way for waiting on this particular thread with the help of boost::lockfree::queue, in case anyone has similar problem:

  nfq_data* data;
  while (!timeToQuit) {
    gdcv.wait(gdlock,[&]{return !gdQueue.empty() || timeToQuit;});
    gdQueue.pop(data);
    gdlock.unlock();
  }

Upvotes: 5

Views: 3647

Answers (4)

David Schwartz
David Schwartz

Reputation: 182753

Even with unique_lock in consumer thread there is a possibility that producer thread will run first, lock the mutex and call noify() before consumer calls wait(), therefore, my app will be missing first nofity() call.

Either the consumer has something to wait for, or it doesn't. If it has something to wait for, there is no problem. If it doesn't have anything to wait for, don't call wait. It really is that simple.

Call wait if, and only if, you want to wait.

Condition variables exist to solve the problem of how you can release a lock and wait without taking the risk that you will wait for something that has already happened. They solve it by providing a function that atomically releases the lock and waits. They cannot miss a wakeup because they hold the lock when they decide to sleep.

Upvotes: 6

Paolo M
Paolo M

Reputation: 12757

No, it's up to you to take care of threads synchronization.

If you don't want to miss a notify call even if it happens before the the consumer starts waiting, you must control this eventuality, recording somewhere that the producer is done, and then not call the wait() function at all.

For example, you can implement a sort of event class, that wait on the condition variable only if the event has not happened yet:

#include <mutex>
#include <condition_variable>

class Event
{
public:
    Event();
    void set_event();
    void reset_event();
    void wait_event();
private:
    std::mutex mtx;
    std::condition_variable cv;
    bool is_set;
};

Event::Event()
: is_set{false}
{}

void Event::set_event()
{
    std::lock_guard<std::mutex> lck{mtx};
    is_set = true;
    cv.notify_all();
}

void Event::reset_event()
{
    std::lock_guard<std::mutex> lck{mtx};
    is_set = false;
}

void Event::wait_event()
{
    std::unique_lock<std::mutex> lck{mtx};
    if( is_set )
        return;

    cv.wait(lck, [this]{ return is_set;} );
}

Upvotes: 2

Jonathan Wakely
Jonathan Wakely

Reputation: 171263

It sounds like you are trying to (mis)use condition_variable to implement a "barrier".

A condition variable allows you to wait for some condition to become true, tested via some predicate, e.g. "there is work available", and you should always test the predicate before waiting, which ensures you don't "miss" the event and wait when you should be working.

Using a condition variable purely to wait, without an associated predicate, does not work well. That's not how they are designed to be used.

If you are trying to make all threads wait at a particular point in the code and only proceed when they have all arrived then you are using a slightly different concept, known as a barrier.

The C++ Concurrency TS defines barriers (and the slightly simpler concept of "latches") for the C++ Standard Library, see the draft N4538.

You can define a barrier yourself by defining a class with a counter, which uses a condition_variable internally. The condition it needs to wait on is "all N threads have incremented the counter". Then you can make the producer and all consumers wait at the barrier, and they will all block until the last thread reaches the barrier. Even if the producer reaches the barrier and starts waiting first, you are guaranteed that the consumers will also stop and wait at the barrier, until all threads reach it, then they will all proceed.

Upvotes: 2

eerorika
eerorika

Reputation: 238311

Even with unique_lock in consumer thread there is a possibility that producer thread will run first, lock the mutex and call noify() before consumer calls wait(), therefore, my app will be missing first nofity() call

If the producer has already run, then the consumer doesn't have to wait and therefore shouldn't call wait. If the consumer only waits when it needs to - and the condition is snychronized - then it cannot miss a notify that it needs to notice.

Upvotes: 1

Related Questions