Using std::conditional_variable to wait on a condition

Justin Bieber picture Justin Bieber · Jan 20, 2017 · Viewed 10.1k times · Source

For simplicity, let's assume that we have only one conditional variable to match a single condition that is reflected by a boolean.

1) Why does std::condition_variable::wait(...) locks the mutex again after a "notify" has been sent to un-sleep it?

2) Seeing the behaviour in "1)", does that mean that when you do std::condition_variable::notify_all it only makes it so that all of the waiting threads are unblocked/woken up... but in order instead of all at once? If so, what can be done to do it all at once?

3) If I only care about threads sleeping until a condition is met and not care a single bit for any mutex acquisition, what can I do? Is there an alternative or should current std::condition_variable::wait(...) approach(es) be hacked around this?

If "hackery" is to be used, will this function work for unblocking all waiting threads on a condition and can it be called from any(per thread) threads:

//declared somehwere and modified before sending "notify"(ies)
std::atomic<bool> global_shared_condition_atomic_bool;

//the single(for simplicity in our case) condition variable matched with the above boolean result
std::condition_variable global_shared_condition_variable;

static void MyClass:wait()
{
    std::mutex mutex;
    std::unique_lock<std::mutex> lock(mutex);

    while (!global_shared_condition_atomic_bool) global_shared_condition_variable.wait(lock);
}

it would have been called from random "waiting" threads like so:

void random_thread_run()
{
    while(someLoopControlValue)
    {
        //random code...
        MyClass:wait(); //wait for whatever condition the class+method is for.
        //more random code...
    }
}

Edit:

Gate class

#ifndef Gate_Header
#define Gate_Header

#include <mutex>
#include <condition_variable>

class Gate
{
public:
    Gate()
    {
        gate_open = false;
    }

    void open()
    {
        m.lock();
        gate_open = true;
        m.unlock();

        cv.notify_all();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(m);

        while (!gate_open) cv.wait(lock);
    }

    void close()
    {
        m.lock();
        gate_open = false;
        m.unlock();
    }

private:
    std::mutex m;
    std::condition_variable cv;
    bool gate_open;
};

#endif

Answer

Yakk - Adam Nevraumont picture Yakk - Adam Nevraumont · Jan 20, 2017

Condition variables wake things up spuriously.

You must have a mutex and it must guard a message of some kind for them to work, or you have zero guarantee that any such wakeup occurred.

This was done, presumably, because efficient implementations of a non-spurious version end up being implemeneted in terms of such a spurious version anyhow.

If you fail to guard the message editing with a mutex (ie, no synchronization on it, the state of the message is undefined behavior. This can cause compilers to optimize the read from memory to skip it after the first read.

Even excluding that undefined behavior (imagine you use atomics), there are race conditions where a message is set, a notification occurs, and nobody waiting on the notification sees the message being set if you fail to have the mutex acquired in the time between the variable being set and the condition variable being notified.

Barring extreme cases, you usually want to use the lambda version of wait.

Auditing condition variable code is not possible unless you audit both the notification code and the wait code.

struct gate {
  bool gate_open = false;
  mutable std::condition_variable cv;
  mutable std::mutex m;

  void open_gate() {
    std::unique_lock<std::mutex> lock(m);
    gate_open=true;
    cv.notify_all();
  }
  void wait_at_gate() const {
    std::unique_lock<std::mutex> lock(m);
    cv.wait( lock, [this]{ return gate_open; } );
  }
};

or

  void open_gate() {
    {
      std::unique_lock<std::mutex> lock(m);
      gate_open=true;
    }
    cv.notify_all();
  }