Thread join
In the Hello World example, you might have noticed the use of t.join() at the end of main() before leaving from the function. The call to join() on the associated thread instance ensures that the launched function will wait until the background thread completes its execution. In the absence of join, the thread will be terminated before the thread starts until the current context is finished (their child threads will also be terminated).
join() is a direct function, either waiting for the thread to finish or not. To get more control over the thread, we have other mechanisms such as mutex, condition variables, and futures, and they will be discussed in the later sections of this chapter and the next chapter. The call to join() cleans up the storage associated with the thread, and so it ensures that the object is no longer associated with the thread that was launched. This asserts that the join() function can only be called once per thread; the call to joinable() will always return false after a call to join(). The previous example with a function object can be modified as follows to understand join():
class parallel_job { int& _iterations; public: parallel_job(int& input): _iterations(input) {} void operator() () { for (int i = 0; i < _iterations; ++i) { some_implementation(i); } } }; void func() { int local_Val = 10000; parallel_job job(local_Val); std::thread t(job); if(t.joinable()) t.join(); }
In this case, at the end of the func() function, the thread object is verified to confirm whether the thread is still in execution. We call joinable() to see its return value before we place the join call.
To prevent the wait on func(), there is a mechanism that was introduced by the standard to continue execution, even if the parent function finishes its execution. This can be achieved using another standard function, detach():
if(t.joinable()) t.detach();
There are a couple of things that we need to consider before detaching a thread; the t thread will probably still be running when func() exits. As per the implementation given in the preceding example, the thread is using the reference of a local variable created in func(), which is not a good idea since the old stack variables can be overwritten at any time on most architectures. These situations must always be addressed while using detach() in your code. The most common way of handling this situation is making a thread self-contained and copying the data into the thread instead of sharing it.