Having fun with Threads on the casual day
In our intro C++ tutorial, we started to explore writing our HelloWorld program going from a simple implementation to one with a little more spice. Here we are going to break down our last example which achieves our goals of global salutations using C++’s std::thread library.
Let’s start with the code.
In this HelloWorld program, we wanted to fire off two threads each print “Hello” and “World” respectively. Sounds simple enough. However, since both threads will begin to execute immediately from creation, we need to control the order in which they both print. This is because there is no way to determine which order the scheduler, a thread execution scheduling system, will run our threads.
So, here is the plan. We will use one thread for each word. We will ensure the second thread cannot print “World” till the first has printed “Hello”. We can orchestrate this behavior using the std::mutex and std::conditional_variable libraries. You can think of a Mutex as a key to the public bathroom at a coffee shop - only one person can use the key and access the bathroom. Others need to wait for the key to be released. A conditional variable allows us to add further locking constraints. In our key to the coffee shop bathroom example, a conditional variable adds a constraint such as even if you have a key, the bathroom needs to be free before getting in if its a single person stall. (You never thought you’d be reading about a bathroom at a coffee shop right? Stay with me here). Thus together, the key and the condition of “bathroom not in use” control ordered access to using the bathroom by multiple patrons - in our case, multiple threads.
With the code and high level approach in mind, let’s walk the code.
First we include our need libraries, “<thread>”, “<mutex>”, “<condition_variable>”, and “<iostream>”. We have a global boolean “goforit” which will serve as our flag for the condition_variable. Next, we define our “mutex” and “condition_variable”. They will come into play in the threads. Now, we introduce our first thread which will print our “Hello”. For our threads, we are passing in Lambdas as input which are anonymous functions containing blocks of code they will execute. In the constructor, we define our lambda by first stating what variables it captures. In this case, our captured variables are for controlling our thread execution ordering. We capture these by reference hence the ampersand qualifier.
In the Lambda block, we immediately print “Hello”. Given that it needs to print first, we can go ahead and make the print. Next we create an additional scope and in it, we create a lock_guard around our created “lock” holding the lock from other threads. We flip our flag “goforit” to true and call notify on our conditional_variable in case a thread is listening. A thread will be listening on our condition_variable if the condition failed and thus will wait to be notified again to check the condition. Our extra scope, created by the curly brackets allows us to let the destruction of created variable to be handled by the runtime. This is great because when our “lock_guard” is destroyed, it releases the key automagically for us.
Our second thread follows a similar pattern. Created with a lambda being passed, the lambda captures our execution control variables but this time it doesn’t print “World” immediately. Since we don’t know if “Hello” was printed, we check our condition_variables before we proceed with printing. First, we create a unique_lock which wraps control of the mutex. It ensures that no matter what non-terminating execution path our thread takes, it will release the thread if we leave scope. We then check our condition_variable. Using our unique_lock, if “goforit“ is true, then we continue to hold the lock and we know we can print. If “goforit” is false, we need to wait. Thus, we release the lock, the thread sleeps, and it waits to be notified.
This ends with our main thread joining the created threads ensuring we only terminate the whole program if the threads are done or more accurately, non-joinable.
This is an interesting way to approach a Hello World implementation. You get to see a little bit of threaded programming and the thinking that you need to ensure to coordinate your threads if they share as resource, in this case the output mechanism.
Let me know what you think or if you have any questions. Also let me know if you find any value here and if not I’m interested to find out. As always, the best way to learn programming is to put it into practice so what are you waiting for, get coding.
With the code and high level approach in mind, let’s walk the code.
First we include our need libraries, “<thread>”, “<mutex>”, “<condition_variable>”, and “<iostream>”. We have a global boolean “goforit” which will serve as our flag for the condition_variable. Next, we define our “mutex” and “condition_variable”. They will come into play in the threads. Now, we introduce our first thread which will print our “Hello”. For our threads, we are passing in Lambdas as input which are anonymous functions containing blocks of code they will execute. In the constructor, we define our lambda by first stating what variables it captures. In this case, our captured variables are for controlling our thread execution ordering. We capture these by reference hence the ampersand qualifier.
In the Lambda block, we immediately print “Hello”. Given that it needs to print first, we can go ahead and make the print. Next we create an additional scope and in it, we create a lock_guard around our created “lock” holding the lock from other threads. We flip our flag “goforit” to true and call notify on our conditional_variable in case a thread is listening. A thread will be listening on our condition_variable if the condition failed and thus will wait to be notified again to check the condition. Our extra scope, created by the curly brackets allows us to let the destruction of created variable to be handled by the runtime. This is great because when our “lock_guard” is destroyed, it releases the key automagically for us.
Our second thread follows a similar pattern. Created with a lambda being passed, the lambda captures our execution control variables but this time it doesn’t print “World” immediately. Since we don’t know if “Hello” was printed, we check our condition_variables before we proceed with printing. First, we create a unique_lock which wraps control of the mutex. It ensures that no matter what non-terminating execution path our thread takes, it will release the thread if we leave scope. We then check our condition_variable. Using our unique_lock, if “goforit“ is true, then we continue to hold the lock and we know we can print. If “goforit” is false, we need to wait. Thus, we release the lock, the thread sleeps, and it waits to be notified.
This ends with our main thread joining the created threads ensuring we only terminate the whole program if the threads are done or more accurately, non-joinable.
This is an interesting way to approach a Hello World implementation. You get to see a little bit of threaded programming and the thinking that you need to ensure to coordinate your threads if they share as resource, in this case the output mechanism.
Let me know what you think or if you have any questions. Also let me know if you find any value here and if not I’m interested to find out. As always, the best way to learn programming is to put it into practice so what are you waiting for, get coding.