Condition variables were the topic of the previous article. I recommend reading it before starting with this article.
Today, we will see another use case of a condition variable: sharing data between the threads.
Work, work, work
Let’s imagine the usual working environment. There is a boss who delegates tasks, and there is a worker who carries out the tasks. We can associate the people with the two different processes. One process is delegating the work and the other one is doing the work. In order to have a successful company, these processes must be synchronized.
Let’s transform the boss–worker example into a concurrent code. We need to keep in mind two important facts while reading the code below.
The two threads will represent the boss and the worker.
The condition variable will synchronize the tasks between the two threads.
We start with include statements and global variables.
Condition variable is associated with:
MUT, which helps with the synchronization.
TASKS, which passes data between the two threads.
There is also a
TODO list and a random distribution. The boss
will randomly choose a task from the
TODO list and assign it
to the worker.
The next section of the code defines a behavior of the boss.
boss() function assigns five tasks to the worker and then
instructs the worker to go home. Of course the boss takes multiple breaks
between her/his work.
add_task() function is the most interesting for us. It
gets a string with the name of the task. Then, it locks the
MUT, because we will modify the shared queue by pushing the
task onto the
TASKS. At the end, the function notifies the
waiting thread with the
notify_one() member function of the
The next section of the code defines a behavior of the worker.
The function contains an infinite loop. The second line of the body of the loop
guarantees that the
TASKS queue is not empty. This means that
the boss already assigned some tasks. We explained the behavior of the
wait() member function in the previous
article. Check it out if you don’t
understand the guarantee.
Afterwards, the function gets and prints a task from the queue. The loop terminates, if the boss commands the worker to go home.
worker() basically waits for the tasks and prints them
until it gets the special task:
"go home". Then the function
returns. This is an example of how to wait and process data which comes in chunks
from another thread.
The main function creates two threads which represent the boss and the worker.
You can look at the entire source code here.
Running the example
One possible output might be the following.
And the corresponding timeline is in the picture below.
The red color represents the boss and the blue color represents the worker. The transmission of the data with the queue is displayed with the black color in the middle.
When running the example, comment out the function
take_break() in the
and run the program multiple times. Then figure out what happens.
We learned how to pass data between the threads using a queue and a condition variable.