MultiThreading
 All Modules Pages

Waiting for one of two conditions

Situation
You are using two worker threads each doing a different piece of work. You want to react as soon as one of the threads has done its part of the work.

Solution concept
Both worker threads are using the same condition variable but they set different boolean flags within your specific data. The predicate within the wait function then can check for these flags and apply arbitrary boolean expressions to them, e.g. flag1 || flag2.

Data definitions shared by all threads:

#include <mutex>
#include <condition_variable>
SomeSpecificDataStructure mySpecificData;
std::mutex myDataMutex;
std::condition_variable myCondVar

Thread A finishes his part of the work and informs the waiting thread C:

// Thread A
{
... // some processing
{
std::lock_guard<std::mutex> guard(myDataMutex);
mySpecificData.threadAHasProcessedData = true;
}
myCondVar.notify_one();
... // continue with some other work
}

In the same way thread B finishes his part of the work and informs the waiting thread C:

// Thread B
{
... // some processing
{
std::lock_guard<std::mutex> guard(myDataMutex);
mySpecificData.threadBHasProcessedData = true;
}
myCondVar.notify_one();
... // continue with some other work
}

Thread C reacts as soon as one of the threads A or B has done its work:

// Thread C
{
...
std::unique_lock<std::mutex> uLock(myDataMutex);
myCondVar.wait(uLock, []{
return mySpecificData.threadAHasProcessedData
|| mySpecificData.threadBHasProcessedData;);
... // continue
}

Waiting for both threads

By changing the boolean expression within the lambda expression you can easily change to waiting until both threads have done their work. Simply replace "||" with "&&".

WaitableCondition - simplified synchronization

Situation:
You only want to send a trigger from thread A to thread B to start its processing. You don't have any data structures which need to be processed concurrently by both threads.

Solution concept:
Use a simple boolean variable as start condition. Embed it together with the corresponding access mutex and the condition variable within a thin wrapper of type WaitableCondition:

// Within your specific namespace MySyncUtils
class WaitableCondition
{
public:
// Constructor
WaitableCondition (bool in_autoReset = true)
:
m_autoReset (in_autoReset),
m_condIsTrue (false)
{}
// Set and reset the condition
void Set (bool in_state)
{
{
std::lock_guard<std::mutex> guard(m_mutex);
m_condIsTrue = in_state;
}
if (in_state) m_conditionVar.notify_one();
}
// Check condition without waiting
bool IsTrue (void)
{
std::lock_guard<std::mutex> guard(m_mutex);
return m_condIsTrue;
}
// Wait until condition becomes true
// If the condition has already been set the function
// will immediately return.
void WaitUntilTrue (void)
{
std::unique_lock<std::mutex> uLock(m_mutex);
m_conditionVar.wait(uLock,[]{return m_condIsTrue});
if (m_autoReset) m_condIsTrue = false;
}
// Wait until condition becomes true (return true)
// or the given timeout has elapsed (return false)
bool TimedWaitUntilTrue (long in_timeoutMs)
{
std::chrono::milliseconds timeoutPeriod (in_timeoutMs);
std::unique_lock<std::mutex> uLock(m_mutex);
if (m_conditionVar.wait_for(uLock,timeoutPeriod,
[]{return m_condIsTrue}))
{
if (m_autoReset) m_condIsTrue = false;
return true;
}
else // timeout
{
return false;
}
}
private:
// Boolean condition several threads are interested in
bool m_condIsTrue;
// Access mutex for reading/writing m_condIsTrue
std::mutex m_mutex;
// Condition variable for triggering a waiting thread
std::condition_variable m_conditionVar;
// optionally reset the condition each time WaitUntilTrue()
// or TimedWaitUntilTrue() has returned
bool m_autoReset;
}

Autoreset
By using the autoreset feature of class WaitableCondition you could trigger thread B several times without any code changes.

Using class WaitableCondition

// Data definitions shared by all threads
MySyncUtils::WaitableCondition myWaitableCondition;

Thread A triggers thread B

// Thread A
{
// prepare something
// trigger thread B to proceed
myWaitableCondition.Set(true);
// continue processing
}

Thread B waits to be triggered by thread A:

// Thread B
{
..
// Wait for trigger before continuing
myWaitableCondition.WaitUntilTrue();
// start/continue processing
}

Because of the embedded logic within WaitableCondition it is not necessary that thread B has already entered waiting state before thread A signals a change of condition.