MultiThreading
 All Modules Pages
Using condition variables - the safe way

Situation:
Some thread A has processed an incoming trigger and wants to inform a waiting thread B to continue with his part of the work.

Schematic solution
Both threads have access to some specific shared data, a corresponding mutex and the condition variable (e.g. the variables may be class attributes and the threads are working with the same class instance):

// Data definitions shared by all threads
#include <mutex>
#include <condition_variable>
// to store status information
SomeSpecificDataStructure mySpecificData; //<##
// to protect access to specific data
std::mutex myDataMutex; //<##
// to trigger rechecking of conditions
std::condition_variable myCondVar; //<##

Basics: with separate checking and waiting

Thread A finishes his part of the work and informs the waiting thread B to start his action:

// Thread A
{
... // do some work
{
std::lock_guard<std::mutex> guard(myDataMutex); //<##
// access specific data and prepare all
// for continuation by thread B
mySpecificData.SomeField = .. //<##
} // release lock and mutex
// Trigger thread B to recheck conditions
myCondVar.notify_one(); //<##
... // continue with some other work
}

If data conditions are not yet fulfilled thread B waits until he receives the signal to recheck conditions:

// Helper function
bool DataAreReadyForProcessing ()
{
// check mySpecificData
// (assumes we are within lock)
return true/false;
}
// Thread B
{
...
//--- wait until data are prepared ---
std::unique_lock<std::mutex> uLock(myDataMutex);
while(!DataAreReadyForProcessing())
{
myCondVar.wait(uLock); // unlocks while waiting
// locks again when returning
// now recheck conditions
}
//--- process data ---
// here the mutex is still/again locked and you can access data
mySpecificData.SomeField = ..

Pay attention to the following points within the code snippet:

For more info see condition_variable - complete reference at CppReference.com

Preferable: Embed checking into wait()

There is also a specialized wait function which allows you to specify the checking code as a predicate. Using the boolean function from above you could simplify the code. The explicitly programmed while loop will disappear (the loop is executed within wait()):

// Thread B
{
...
std::unique_lock<std::mutex> uLock(myDataMutex);
myCondVar.wait(uLock, DataAreReadyForProcessing); //<##
mySpecificData.SomeField = ..
}

As an alternative to a boolean function you could also pass a function object or a lambda expression:

// passing a lambda expression
myCondVar.wait(uLock, []{return mySpecificData.readyToProcess;});

Time limited waiting

To avoid indefinite blocking of a thread when a condition does not come true you could specify a maximum wait time. The wait will return either when the condition is fulfilled or when the timeout has elapsed. It is your task to analyze the reason why wait() has returned.

When using the wait() function without predicate you get a return value std::cv_status::timeout or std::cv_status::no_timeout:

// waiting for timeout after 5 seconds
std::chrono::seconds timeoutPeriod = 5;
auto timePoint = std::chrono::system_clock::now() + timeoutPeriod;
std::unique_lock<std::mutex> uLock(myDataMutex);
while(!DataAreReadyForProcessing())
{
if (myCondVar.wait_until(uLock, timePoint) //<##
== std::cv_status::timeout)
{
// data conditions where not fulfilled within
// the time period; e.g. do some error handling
break;
}
}

When using the wait() function with predicate you get a boolean return value (false means "timeout has elapsed"):

if (myCondVar.wait_for(uLock, timeoutPeriod,
DataAreReadyForProcessing))
{
// data conditions where fulfilled
// regular processing
}
else // timeout occured, conditions are not fulfilled
{
// e.g. do some error handling
}
Tip:
Always prefer passing a predicate to the wait() functions. Then your code will stay more simple.