In this article we will discuss about joining and detaching of std::thread.
Joining Threads with std::thread::join()
Once a thread is started, then that thread is a joinable thread. Another thread can wait for this new thread to finish. For this another need need to call join()
function on the std::thread
object i.e.
std::thread th(funcPtr); // Some Code th.join();
Lets understand this with an example.
In the example below, we will create a vector of thread objects, and add 10 thread objects to this vector. Each thread object utilizes a function object, termed WorkerThreadFunctor
, as its designated function. Once all threads are instantiated and begin executing, we will process several lines within the main function. Subsequently, we will wait for all threads to complete their execution by invoking the join()
method for each thread within the vector. In this scenario, we will ensure every thread concludes by calling the join()
method.
The complete example is as follows:
#include <iostream> #include <thread> #include <algorithm> class WorkerThreadFunctor { public: void operator()() { std::cout<<"Worker Thread "<<std::this_thread::get_id()<<" is Executing"<<std::endl; } }; int main() { // Create a Vector of Thread std::vector<std::thread> threadList; // Add 10 thread objects in vector for(int i = 0; i < 10; i++) { threadList.push_back( std::thread( WorkerThreadFunctor() ) ); } std::cout<<"wait for all the worker thread to finish"<<std::endl; // Now wait for all the worker thread to finish i.e. // Call join() function on each of the std::thread object for (auto& th: threadList) { th.join(); } std::cout<<"Exiting from Main Thread"<<std::endl; return 0; }
Output:
Worker Thread 139872044054080 is Executing Worker Thread 139872035661376 is Executing Worker Thread 139872027268672 is Executing Worker Thread 139872018875968 is Executing Worker Thread 139872010483264 is Executing Worker Thread 139871918224960 is Executing Worker Thread 139872002090560 is Executing Worker Thread 139871901439552 is Executing wait for all the worker thread to finish Worker Thread 139871909832256 is Executing Worker Thread 139871893046848 is Executing Exiting from Main Thread
Detaching Threads using std::thread::detach()
Instead of joining, we can detach a thread. By detaching, we convert threads into daemon or background threads. To detach a thread, the detach()
method must be called on the respective thread object. After invoking detach()
, the thread object is no longer tied to the actual thread of execution. For example,
// Create a Thread std::thread th(funcPtr); // Detach the thread i.e. // thread will not be joinable now th.detach();
After calling detach()
, the std::thread
object is no longer associated with the actual thread of execution.
Important Points about join() and detach() functions
There are vital points to consider when using join()
or detach()
:
- Never invoke
join()
ordetach()
on a thread object that isn’t associated with an executing thread: When thejoin()
method is called on a thread object, it waits for the associated thread to finish its execution. Oncejoin()
has been executed, the thread object is disassociated from any executing thread. Invokingjoin()
again on such an object would crash the program.
std::thread threadObj( (WorkerThread()) ); threadObj.join(); threadObj.join(); // It will cause Program to Terminate
It will terminate the program abruptly.
- Avoid calling
detach()
repeatedly: Callingdetach()
on a thread object detaches the thread object from its executing function. Ifdetach()
is invoked twice on the same thread object, the program will crash.
std::thread threadObj( (WorkerThread()) ); threadObj.detach(); threadObj.detach(); // It will cause Program to Terminate
or
std::thread threadObj( (WorkerThread()) ); threadObj.join(); threadObj.detach(); // It will cause Program to Terminate
In both the scenarios program will be terminated abruptly.
To ensure safety before calling join()
or detach()
, it’s advisable to check if the thread is joinable using the joinable()
method, as demonstrated in the given example.
// Create thread object std::thread threadObj( (WorkerThread()) ); // Check if thread is joinable if(threadObj.joinable()) { std::cout<<"Detaching Thread "<<std::endl; threadObj.detach(); } // Check if thread is joinable if(threadObj.joinable()) { std::cout<<"Detaching Thread "<<std::endl; threadObj.detach(); } // Create thread object std::thread threadObj2( (WorkerThread()) ); // Check if thread is joinable if(threadObj2.joinable()) { std::cout<<"Joining Thread "<<std::endl; threadObj2.join(); } // Check if thread is joinable if(threadObj2.joinable()) { std::cout<<"Joining Thread "<<std::endl; threadObj2.join(); }
Never forget to call either join() or detach()
If neither join()
nor detach()
is called on a std::thread
object that has an associated executing thread, then the program will terminate when that thread object’s destructor is invoked. This is because the destructor checks if the thread is still joinable; if it is, the program is forcibly terminated to avoid undefined behavior. For example,
#include <iostream> #include <thread> #include <algorithm> class WorkerThread { public: void operator()() { std::cout<<"Worker Thread "<<std::endl; } }; int main() { std::thread threadObj( (WorkerThread()) ); // Program will terminate as we have't called either join or detach with the std::thread object. // Hence std::thread's object destructor will terminate the program return 0; }
Output:
terminate called without an active exception Aborted (core dumped)
Thread and RAII in C++11 / C++14
We should not forget call either join() or detach() in case of exceptions, otherwise it can terminate the program. To prevents with we should use RESOURCE ACQUISITION IS INITIALIZATION (RAII) i.e.
#include <iostream> #include <thread> class ThreadRAII { std::thread & m_thread; public: ThreadRAII(std::thread & threadObj) : m_thread(threadObj) { } ~ThreadRAII() { // Check if thread is joinable then detach the thread if(m_thread.joinable()) { m_thread.detach(); } } }; void thread_function() { for(int i = 0; i < 5; i++) { std::cout<<"thread_function Executing"<<std::endl; } } int main() { std::thread threadObj(thread_function); // If we comment this Line, then program will crash ThreadRAII wrapperObj(threadObj); return 0; }
To address this problem, we can leverage the RAII (Resource Acquisition Is Initialization) principle. By creating a wrapper around the std::thread
class, we can ensure safe thread management. When the wrapper’s destructor is invoked, it will automatically check if the internal thread object is joinable. If it’s joinable, the wrapper will then invoke the detach()
method. This approach provides a safeguard: even if the developer forgets to call join()
or detach()
, the application will not terminate unexpectedly.
Summary
Today, we delved deep into the join()
and detach()
functions of std::thread
. We explored how to appropriately join or detach a joinable thread in C++.
Pingback: C++11 Multithreading – Part 1 : Three Different ways to Create Threads – thisPointer.com