Skip to Content
Course content

106: Joining and Detaching Threads

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think of starting a thread like hiring a freelance contractor to do a specific job for you. Once you've handed them the instructions and the tools, you have a choice in how you handle the end of that relationship. You can either wait at the door until they finish the job and hand you the keys back (that's joining), or you can tell them, "Just finish whenever you're done, I don't need to know when it's over" (that's detaching).

In C++, if you hire a contractor (create a std::thread object) and then just walk away or "fire" the object (let it go out of scope) without explicitly choosing one of those two options, C++ doesn't just let it slide. It assumes you've made a catastrophic logic error and will call std::terminate(), crashing your entire program. I've seen plenty of juniors spend hours debugging a crash only to realize they forgot to call .join().

Waiting for the Work to Finish

When you call .join(), the calling thread (usually your main thread) stops dead in its tracks. It won't execute another line of code until the thread you joined has completely finished its execution. This is your synchronization point.

#include <iostream>
#include <thread>
#include <vector>
#include <numeric>

void sum_large_array(const std::vector<int>& data, long long& result) {
    result = std::accumulate(data.begin(), data.end(), 0LL);
}

int main() {
    std::vector<int> numbers(1000000, 1);
    long long total = 0;

    // We start the thread to do the heavy lifting
    std::thread worker(sum_large_array, std::ref(numbers), std::ref(total));

    // If we tried to print 'total' right here, it would likely be 0 
    // because the worker is still running in the background.

    worker.join(); // We wait here. The main thread pauses.

    std::cout < "The total is: " < total < std::endl; 
    return 0;
}

In this case, join() is non-negotiable. We can't print the result until the calculation is actually done. By joining, we ensure the worker thread has finished its work and its resources are cleaned up before we move on.

Setting it Free (and the Risks Involved)

Sometimes, you have a task that is "fire and forget." Maybe it's a background logger that writes telemetry to a file, or a heartbeat signal that pings a server every ten seconds. You don't want your main application to freeze just to wait for a log entry to be written to a slow disk.

That's where .detach() comes in. It severs the connection between the std::thread object and the actual OS thread. The thread keeps running in the background, but you no longer have a handle to control it or wait for it.

#include <iostream>
#include <thread>
#include <chrono>

void background_logger() {
    while (true) {
        std::cout < "[Log]: System healthy... " < std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
}

int main() {
    std::thread logger(background_logger);
    
    logger.detach(); // We tell the OS: "Just let this run on its own."

    std::cout < "Main application is running independently!" < std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    
    return 0; // When main exits, the detached thread is killed abruptly.
}

Now, a word of caution from someone who has spent too many nights chasing memory corruption: be incredibly careful with detached threads. Because the thread is running independently, it might try to access a variable or an object that the main thread has already destroyed. If background_logger tried to access a local variable from main() after main() had returned, you'd have a dangling reference and a very nasty crash.

Deciding Which Path to Take

I generally advise leaning toward join(). It's safer, more predictable, and makes the lifetime of your data much easier to reason about. Use detach() only when the task is truly independent and doesn't rely on any resources that could possibly be destroyed before the thread finishes.

Just remember: every std::thread you instantiate must end in either a join() or a detach() before the object is destroyed. No exceptions.




📋 Practical Task

Exercise: The Asynchronous File-Writer and Processor

You are building a simplified data processing pipeline. You need to implement a program that does two things:

  • Processes a "heavy" dataset (represented by a sleep timer) and provides a result that the main program must use.
  • Runs a background "Heartbeat" monitor that prints a status message every second, which the main program should not wait for.

Requirements:

  1. Create a function void processData(int& result) that sleeps for 2 seconds and then sets the result to 42.
  2. Create a function void heartbeat() that prints "Heartbeat active..." in a loop every 1 second.
  3. In main(), start both threads.
  4. Correctly use join() and detach() so that the program prints the result of processData, but allows the heartbeat to run in the background without blocking the final output.
  5. Ensure the program does not crash upon exit.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.