Before, we talked about the basics of how C++ threads are
used, and how threads can protect data by using mutexes,
lock_guards, unique_locks, recursive_mutex, and once_flags.
Now, we talk about how threads can wait for other threads to complete tasks.
There are multiple ways to check when a condition becomes true:
condition_variable and condition_variable_anyLet’s use a case study:
mutex m;
queue<data> q;
condition_variable c;
void prepare(){
while(more_data()){
data d = prepare_data();
lock_guard l(m);
q.push(data);
c.notify_one(); // notify a waiting thread!
}
}
void process(){
while(true){
unique_lock l(m);
c.wait(l, []{return !q.empty();}); // notified!
data d = q.front();
q.pop();
l.unlock();
process(d);
}
}
What happens here is that wait(lock, boolfunc) actually unlocks the
mutex and sets the thread to sleep.
Specifically, what wait(lock, boolfunc) does is the following:
boolfunction(could be a lambda like in e.x.)As you can see, we can’t simply use lock_guard to perform these operations, since
(2) needs to unlock the lock.
wait()Actually, wait() does not need to check the bool function once - it could check it for any number of times.
In addition, a situation could occur, called a “spurious wake”. In this case, threads can
be woken up regardless of whether they were notified, and they will test the boolfunc.
safe_queueA safe_queue should support the following functions:
safe_queue(const safe_queue& other);
void push(T new_value); // Adds new value into queue
bool wait_and_pop(T& value); // assign popped front() into value
bool empty(); // checks whether any element left
We will have the following members:
mutex m;
queue<T> q;
condition_variable c;
For the copy constructor:
safe_queue(const safe_queue& other){
// lock the other queue so we can copy their values safely
lock_guard<mutex> l(other.m);
q = other.q;
}
For push:
void push(T new_value){
lock_guard<mutex> l(m);
q.push(new_value);
c.notify_one();
}
For wait_and_pop:
void wait_and_pop(T& value){
unique_lock<mutex> l(m);
c.wait(l, [this]{ return !q.empty(); });
value = q.front();
q.pop();
}
For empty:
bool empty(){
lock_guard<mutex> l(m);
return q.empty();
}