From the course: Parallel and Concurrent Programming with C++ Part 2

Condition variable: C++ demo - C++ Tutorial

From the course: Parallel and Concurrent Programming with C++ Part 2

Start my 1-month free trial

Condition variable: C++ demo

- [Instructor] This C++ program to demonstrate using a condition variable has a function named hungry_person on line 10, which has an input parameter for an ID number. The hungry_person function will run as a thread that alternates with other hungry people threads to take servings of soup until it's all gone. The global variable on line seven represents the number of soup servings left, and the slow cooker lid on line eight is the mutex to protect the soup servings variable, so only one hungry person can change it at a time. Down within the while loop on line 12, the hungry person uses a unique lock to lock the slow cooker lid, then the if statement on line 14 compares their ID number with the number of soup servings remaining. It does that module 02, because in this example, we create two hungry person threads down in the main function. If it is the current thread's turn and there's still soup left, then the hungry person will take soup by decrementing the number of soup servings on line 15. Otherwise, the hungry person will put back the lid on line 17 and check again for their turn on the next loop iteration. Put_lid_back is a local variable we declared in the function on line 11 to keep track of how many times this particular thread puts back the lid because it's not their turn. At the end of the hungry_person function, we print a message with that value on line 20 to see what happened. Now I'll switch over to a command prompt, open to the folder with that example code, and I'll use the make command to build the program and then run the executable condition_variable_demo.exe. The two threads will alternate taking servings of soup, and then, at the end, we can see that person zero put the lid back over a thousand times. That's a lot of wasted cycles checking to see that it's not their turn for soup, so let's use a condition variable to help them coordinate. The typical usage pattern for a condition variable involves first locking the mutex that we'll be using with the condition variable and then using a while loop to check for some condition. If the condition is not true, then we need to wait. Calling the condition variable's wait function will release this thread's lock on the mutex and cause it to wait here. Now, I want to emphasize here that the condition variable is not the condition itself, or an event. The condition that we're checking for is the logic of the while loop, is it this thread's turn to take soup? The condition variable is just a place or mechanism for threads to wait. When the waiting thread gets signaled by another thread, it will wake up, lock the mutex, and then check the while loop's condition again. If the condition is true this time, then we'll continue past the loop to execute the critical section of code. Now, one important reason for placing the condition variable's wait function inside of a while loop like this is that in certain operating environments, the condition variable could have what is called a spurious wakeup, meaning it wakes up from its waiting state when it's not supposed to. By placing it inside of a while loop, if a spurious wakeup occurs, the thread will see that it's still not time to continue on and it will go back to sleeping. To implement that in our hungry_person function, we'll include the condition variable header at the top of our program. Then we'll create a new condition variable object on line 10 named soup_taken. Next, we'll change the if statement on line 16 into a while loop and we'll modify the condition it checks for to see if it's not this hungry person's turn to take soup. If it's not their turn, then we'll have the thread wait on the condition variable to get signaled after another thread takes soup. Notice that we're passing in the lid_lock, so that the wait function knows which lock to release and then reacquire later when this thread gets signaled. We'll also move incrementing the put_lit_back counter into the while loop to track how many times each thread has to wait because it was not their turn. I'll close out the while loop. And then clean up this hanging else statement. Now, if the thread checks the condition and it is their turn then execution will continue past the while loop. We need to add another check to make sure that there is still soup left. If there is soup left, the thread will take soup by decrementing the soup servings. And then, finally, it will need to signal the other thread to wake up. To do that, we'll release our lock on the lid. And then call the condition variable's notify_one function to signal the other hungry person that soup was taken, so it should be their turn. After saving those changes, I'll switch back over to the console to build and then run the program. And now we have two hungry threads that are taking turns and coordinating their actions to avoid wasting a whole lot of energy unnecessarily checking whose turn it is. Now, let's see what happens if we expand this dinner party to include more hungry people by modifying the program's main function to create five hungry people threads instead of just two. We'll also need to modify the condition statement on line 16 to rotate servings among the five people. Now, if I build... And try to run the program again, it gets stuck. So I'll press control break to manually terminate the program. The problem here is that we used the notify_one function on line 23, which only wakes up one of the waiting threads. Of those four other threads, if it doesn't wake up the one whose turn it is next, then the program will get stuck. The fix here is to change that function to notify_all to wake up all of the waiting threads to check and see if it's their turn. Now when I build and run the program... Everything works great. All of the threads eat, and they take turns doing so. If you only need to signal one waiting thread and you don't care which one it is, then the basic notify_one function will work fine. But in this example, since we wanted a specific thread to wake up and see that it's their turn, relying on the single notify_one function to wake up the right thread will not always work.

Contents