Question

I'm looking for a way to wait for multiple condition variables. ie. something like:

boost::condition_variable cond1;  
boost::condition_variable cond2;

void wait_for_data_to_process()  
{  
    boost::unique_lock<boost::mutex> lock(mut);

    wait_any(lock, cond1, cond2); //boost only provides cond1.wait(lock);

    process_data();
}

Is something like this possible with condition variables. And if not are there alternative solutions?

Thanks

Was it helpful?

Solution

I don't believe you can do anything like this with boost::thread. Perhaps because POSIX condition variables don't allow this type of construct. Of course, Windows has WaitForMultipleObjects as aJ posted, which could be a solution if you're willing to restrict your code to Windows synchronization primitives.

Another option would to use fewer condition variables: just have 1 condition variable that you fire when anything "interesting" happens. Then, any time you want to wait, you run a loop that checks to see if your particular situation of interest has come up, and if not, go back to waiting on the condition variable. You should be waiting on those condition variables in such a loop anyways, as condition variable waits are subject to spurious wakeups (from boost::thread docs, emphasis mine):

void wait(boost::unique_lock<boost::mutex>& lock)
...
Effects:
Atomically call lock.unlock() and blocks the current thread. The thread will unblock when notified by a call to this->notify_one() or this->notify_all(), or spuriously. ...

OTHER TIPS

As Managu already answered, you can use the same condition variable and check for multiple "events" (bool variables) in your while loop. However, concurrent access to these bool variables must be protected using the same mutex that the condvar uses.

Since I already went through the trouble of typing this code example for a related question, I'll repost it here:

boost::condition_variable condvar;
boost::mutex mutex;
bool finished1 = false;
bool finished2 = false;

void longComputation1()
{
    {
        boost::lock_guard<boost::mutex> lock(mutex);
        finished1 = false;
    }
    // Perform long computation
    {
        boost::lock_guard<boost::mutex> lock(mutex);
        finished1 = true;
    }
    condvar.notify_one();
}

void longComputation2()
{
    {
        boost::lock_guard<boost::mutex> lock(mutex);
        finished2 = false;
    }
    // Perform long computation
    {
        boost::lock_guard<boost::mutex> lock(mutex);
        finished2 = true;
    }
    condvar.notify_one();
}

void somefunction()
{
    // Wait for long computations to finish without "spinning"
    boost::lock_guard<boost::mutex> lock(mutex);
    while(!finished1 && !finished2)
    {
        condvar.wait(lock);
    }

    // Computations are finished
}
alternative solutions?

I am not sure of Boost library but you can use WaitForMultipleObjects Function to wait for multiple kernel objects. Just check if this helps.

As Managu points out using multiple conditions might not be a good solution in the first place. What you want to do should be possible to be implemented using Semaphores.

Using the same condition variable for multiple events technically works, but it doesn't allow encapsulation. So I had an attempt at making a class that supports it. Not tested yet! Also it doesn't support notify_one() as I haven't worked out how to implement that.

#pragma once

#include <condition_variable>
#include <unordered_set>

// This is like a `condition_variable` but you can wait on multiple `multi_condition_variable`s.
// Internally it works by creating a new `condition_variable` for each `wait_any()` and registering
// it with the target `multi_condition_variable`s. When `notify_all()` is called, the main `condition_variable`
// is notified, as well as all the temporary `condition_variable`s created by `wait_any()`.
//
// There are two caveats:
//
//  1. You can't call the destructor if any threads are `wait()`ing. This is difficult to get around but
//     it is the same as `std::wait_condition` anyway.
//
//  2. There is no `notify_one()`. You can *almost* implement this, but the only way I could think to do
//     it was to add an `atomic_int` that indicates the number of waits(). Unfortunately there is no way
//     to atomically increment it, and then wait.
class multi_condition_variable
{
public:
    multi_condition_variable()
    {
    }

    // Note that it is only safe to invoke the destructor if no thread is waiting on this condition variable.
    ~multi_condition_variable()
    {
    }

    // Notify all threads calling wait(), and all wait_any()'s that contain this instance.
    void notify_all()
    {
        _condition.notify_all();
        for (auto o : _others)
            o->notify_all();
    }

    // Wait for notify_all to be called, or a spurious wake-up.
    void wait(std::unique_lock<std::mutex>& loc)
    {
        _condition.wait(loc);
    }

    // Wait for any of the notify_all()'s in `cvs` to be called, or a spurious wakeup.
    static void wait_any(std::unique_lock<std::mutex>& loc, std::vector<std::reference_wrapper<multi_condition_variable>> cvs)
    {
        std::condition_variable c;
        for (multi_condition_variable& cv : cvs)
            cv.addOther(&c);
        c.wait(loc);
        for (multi_condition_variable& cv : cvs)
            cv.removeOther(&c);
    }

private:
    void addOther(std::condition_variable* cv)
    {
        std::lock_guard<std::mutex> lock(_othersMutex);
        _others.insert(cv);
    }

    void removeOther(std::condition_variable* cv)
    {
        // Note that *this may have been destroyed at this point.
        std::lock_guard<std::mutex> lock(_othersMutex);
        _others.erase(cv);
    }

    // The condition variable.
    std::condition_variable _condition;

    // When notified, also notify these.
    std::unordered_set<std::condition_variable*> _others;

    // Mutex to protect access to _others.
    std::mutex _othersMutex;
};

// Example use:
//
//  multi_condition_variable cond1;
//  multi_condition_variable cond2;
//
//  void wait_for_data_to_process()
//  {
//      unique_lock<boost::mutex> lock(mut);
//
//      multi_condition_variable::wait_any(lock, {cond1, cond2});
//
//      process_data();
//  }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top