Condition variable
Another big topic of concurrent programming is how to synchronize the threads between each other. There are several techniques which allow us to do the synchronization. Today, we will study synchronization mechanism called a condition variable.
The condition variable is associated with an event or condition. Roughly speaking, a thread is doing some of the following activities
-
changing the condition and notifying other threads about it,
-
waiting for the condition to be satisfied.
Let’s clarify the abstractions above with an example.
Mother and son with the laundry
There is a family with a mother and a son. The son is playing basketball and he is sweating, because he is playing hard. Afterwards, he asks the mother to do the laundry for him. She is nice and does it. The son is happy, because he has fresh shirt and shorts. (Any connection with the author of this article is merely apparent :-).)
We will put this short story into the code. There are two processes:
-
the mother is doing the laundry,
-
the son is playing basketball and outsourcing the laundry to his mother.
These two processes will run in separate threads. There is a communication/synchronization between them which is done via a condition variable.
Let’s begin with the code. We start with include statements and global variables. We would avoid the global variables in serious code and use classes with data members. But it is OK to use global variables for this demonstration.
Because we will use a condition variable, we need to include
<condition_variable>
header. The condition variable
CV
is associated with two things:
-
The mutex
MUT
, which helps with the synchronization. -
The enum
SONS_LAUNDRY
, which describes the state of the laundry.
The next two functions check the state of the laundry and return true if the laundry is clean or dirty.
The next section of code describes the behavior of the mother.
The function acquires an unique lock on the mutex. Then it calls a
wait()
member function, of the CV
object,
passing the lock and the function is_laundry_dirty
. The
wait()
function checks the condition, by calling
is_laundry_dirty()
. There are two possibilities:
-
If the condition is satisfied – the laundry is dirty –
wait()
returns and the program continues. -
If the condition is not satisfied – the laundry is clean –
wait()
unlocks the mutex and puts the thread in a waiting state. The thread stops waiting when anotify_one()
member function of the condition variable is called. When the thread stops waiting, it reacquires the lock of the mutex and checks the condition again, by callingis_laundry_dirty()
. Thewait()
returns only ifis_laundry_dirty()
returnstrue
, otherwisewait()
starts waiting again.
We must use a std::unique_lock
, because
wait()
unlocks the mutex and locks it again. This is not
possible with the std::lock_guard
.
Once the condition is satisfied, we change the SONS_LAUNDRY
to Loundry::CLEAN
. (The mother does the laundry.) Afterwards,
the function unlocks the mutex and notifies the condition variable by calling
notify_one()
. The notification is aiming the other thread
(the son).
The next section of code describes the behavior of the son.
The function prints some information and then locks the mutex. The mutex is
automatically unlocked, after we set the SONS_LAUNDRY
to
dirty, because we put the lock guard object inside a new scope.
Then, the function notifies the waiting thread (the mother).
Afterwards, we start waiting for the clean laundry. The code is again inside a
new scope and it is similar to the beginning of the
clean_laundry()
function. The difference is that we are
waiting for the clean laundry and not for the dirty one.
At the end, the printing indicates that the mother cleaned the laundry.
The main function is simple. It creates two threads: mother
and son
with functions clean_laundry
and
play_around
, respectively.
You can look at the entire source code here.
Running the example
The code might be a bit complicated at first sight. The figure can help us understand the timeline of the execution of the program.
The red color represents the mother
’s thread, the blue color
represents the son
’s thread and the black color in the middle
represents condition variable.
The mother
starts with waiting
for the dirty laundry, while the son
is playing
basketball.
Then, the son
, via the condition variable, asks
the mother
to do the laundry for him. The
son
is then waiting for the clean laundry while the
mother
does the laundry.
Finally, the mother
, via the condition variable, informs the
son
about the clean laundry and the son
lives happily ever after.
We can also verify the code by running it and looking at the output.
Summary
We learned one of the techniques for thread synchronization.
Links:
-
Source code: conditionVariable.cpp