Condition Variables
-
Suppose we have two threads
- "Writer" thread modifies a shared string
- "Reader" thread uses the modified string
-
The two threads need to be coordinated
-
We must also avoid a data race
-
One solution is to introduce a condition variable
-
Scenario
- Thread A tells the condition variable it is waiting
- Thread B notifies the condition variable when it updates the string
- The condition variable wakes thread A up
- Thread A then uses the string
Condition Variable and Thread Safety
- We use a mutex to protect critical sections
- The condition variable also uses the same mutex
- Thread coordination
- No data race
std::condition_variable
-
Defined in
<condition_variable> -
wait()- Takes an argument of type
std::unique_lock - It unlocks its argument and blocks the thread until a notification is received
- Takes an argument of type
-
wait_for()andwait_until()- Relock their argument if a notification is not received in time
-
notify_one()- Wake up one of the waiting threads
- The scheduler decides which thread is woken up
-
notify_all()- Wake up all the waiting threads
Condition Variable Scenario
-
Thread A locks the mutex
- It calls the
condition_variable'swait()member function - The condition variable unlocks the mutex
- The condition variable blocks this thread
- It calls the
-
Thread B locks the mutex
- It modifies the string and unlocks the mutex
- It calls
notify_one()
-
The condition variable wakes thread A up
- The
wait()call returns with the mutex locked - Thread A resumes execution and uses the string
- The
Reader thread
// Waiting thread
void reader() {
// Lock the mutex
std::unique_lock<std::mutex> uniq_lck(mut);
// Call wait() on the condition variable
// Unlocks the mutex and makes this thread sleep
cond_var.wait(uniq_lck);
// The condition variable wakes this thread up and locks the mutex
// Use the shared data
}
Writer thread
// Notifying thread
void writer() {
{
// Lock the mutex
std::lock_guard<std::mutex> lck_guard(mut);
// Modify the shared data
sdata = "Populated";
} // Release the lock
// Notify the condition variable
cond_var.notify_one();
}
Code Example
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
using namespace std::literals;
std::string sdata;
// To protect critical sections
std::mutex mut;
std::condition_variable cond_var;
void reader() {
// Lock the mutex
std::cout << "Reader thread locking mutex\n";
std::unique_lock<std::mutex> uniq_lck(mut);
std::cout << "Reader thread had locked the mutex\n";
// Sleep until waken up by cond_var
std::cout << "Reader thread sleeping...\n";
cond_var.wait(uniq_lck);
std::cout << "Reader thread wakes up\n";
std::cout << "Data is \"" << sdata << "\"\n";
}
void writer() {
{
std::cout << "Writer thread locking mutex\n";
// This will not be explicitly unlocked
std::lock_guard<std::mutex> lck_guard(mut);
std::cout << "Writer thread had locked the mutex\n";
std::this_thread::sleep_for(2s);
std::cout << "Writer thread modifying data...\n";
sdata = "Populated";
}
// Notify the condition variable
std::cout << "Writer thread sends notification\n";
cond_var.notify_one();
}
int main() {
sdata = "Empty";
std::cout << "Data is \"" << sdata << "\"\n";
std::thread read(reader);
std::thread write(writer);
write.join();
read.join();
}
std::condition_variable_any
-
std::condition_variableonly works withstd::mutex- Does not work with
std::timed_mutex
- Does not work with
-
There is also
std::condition_variable_any- Works with any mutex-like object
- Including our own types
- May have more overhead than
std::condition_variable