Join & Detach Threads in C++11 / C++14? Multithreading – Part 2

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():

  1. Never invoke join() or detach() on a thread object that isn’t associated with an executing thread: When the join() method is called on a thread object, it waits for the associated thread to finish its execution. Once join() has been executed, the thread object is disassociated from any executing thread. Invoking join() 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.

  1. Avoid calling detach() repeatedly: Calling detach() on a thread object detaches the thread object from its executing function. If detach() 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++.

1 thought on “Join & Detach Threads in C++11 / C++14? Multithreading – Part 2”

  1. Pingback: C++11 Multithreading – Part 1 : Three Different ways to Create Threads – thisPointer.com

Comments are closed.

Scroll to Top