Search code examples
c++multithreadingstdthread

Check if an std::thread finished execution without std::thread::join()


I found this stack-overflow question about this but it is 11 years old and I was wondering if there's a better way to do this as the question was posted in C++11 times.

There is std::thread::joinable() but the problem is that even if the thread finished executing, it will still be marked as joinable, if it hasn't been joined with std::thread::join().

A thread that has finished executing code, but has not yet been joined is still considered an active thread of execution and is therefore joinable. docs

I could use an std::atomic<bool> threadFinished = false; and set it to true in the thread right before it finishes (this is mentioned in the answer of the stack-overflow question I mentioned at the beginning of this question).

Something like this:

std::atomic<bool> threadFinished = false;

std::thread myThread([&threadFinished]() {
    std::this_thread::wait(1000);
    threadFinished = true;
});

while (true) {
    if (threadFinished) {
        // Thread finished executing (note: myThread.joinable() is still true)
        break;
    }
}

myThread.join();

But this seems very unclean and unscalable, especially if you have many return points in the thread. Is there a better way to accomplish this?


Solution

  • But this seems very unclean and unscalable, especially if you have many return points in the thread. Is there a better way to accomplish this?

    To fix the unclean problem: RAII

    struct ThreadFinishedSignaler
    {
        ~ThreadFinishedSignaler(){ threadFinished = true; }
    }
    

    then in the thread:

    ThreadFinishedSignaler sig;
    doWork();
    if(something)
        stopWorking(); // automatically sets true with destructor
    doSomeWork();
    // or here, automatically again, once the scope is gone
    

    To fix the scalability: Have multiple atomics or mutexes working in parallel, 1 per thread:

    struct NonFalseSharingMutexLock
    {
        std::mutex mut;
        char augmentation[CacheLineSize - sizeof(std::mutex)];
    }
    

    then in the main thread:

    NonFalseSharingMutexLock locks[numThreads];
    

    finally in worker threads:

    std::lock_guard<std::mutex> lg(locks[myThreadId]);
    // change stuff
    

    If main thread is not supposed to check an array of states, then you can combine the results as a "graph" with every 2 thread combining their results into 1 then other 2 threads do same, then 2 results combined into 1, ... like a graph or tree with no thread checking more than 2 results (less locking contention).

    If the work done by each thread is big enough, you can simply use atomic integer:

    In main thread:

    std::atomic<int> numThreadsWorking = 32;
    

    In worker threads:

    struct ThreadFinishedSignaler
    {
        ~ThreadFinishedSignaler(){ numThreadsWorking--; }
    }
    
    ThreadFinishedSignaler sig;
    // exit anywhere, it just destructs and decrements automatically
    

    when numThreadsWorking reaches zero, all work is complete.