1

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?

  • 1
    "*But this seems very unclean and unscalable [...]*" - in this case I would inherit from `std::jthread` privately and add the functionality you desire to be handled internally by your class. – Fureeish Jul 22 '23 at 13:55
  • So there isn't any built-in functionality for this? – Sandu Chicu Jul 22 '23 at 13:57
  • Why do you need that? If you want to wait until the thread is finished, then just join it, that's exactly what `join` does. – n. m. could be an AI Jul 22 '23 at 13:59
  • I think the value returned by `std::thread::get_id` might give you that information. Worth trying that, at any rate. – Paul Sanders Jul 22 '23 at 14:01
  • @PaulSanders `std::thread::get_id` returns an id if its joinable, otherwise a default constructed instance – Sandu Chicu Jul 22 '23 at 14:03
  • OK, well, that sounds promising. But, to me, the question is: what does it return after the thread terminates? – Paul Sanders Jul 22 '23 at 14:04
  • No, there isn't any built-in way to do this, because it seems like it would not be possible to implement it in a portable way that would introduce no overhead. I am also a fan of adding as much as possible into the standard library, but C++ evolves in a different way. – Fureeish Jul 22 '23 at 14:04
  • 2
    I fail to see what concern you're trying to address. Your thread function is (atomically) setting `threadFinished` to `true` immediately before it terminates. Your other thread is waiting for `threadFinished` to be `true` and then calling `myThread.join()`. Since `myThread.join()` will wait for `myThread` to complete, all of your machinery is just adding additional complication but achieving no more. – Peter Jul 22 '23 at 14:06
  • As you've observed, `joinable` has nothing to do with whether a thread has completed. It tells you whether a call to `join` is valid; you can't `join` a default-constructed thread and you can't `join` a thread that 's been `detach`ed and you can't `join` a thread that's already been `join`ed. That's what `joinable` tells you. – Pete Becker Jul 22 '23 at 14:07
  • @Peter This is just a part of the code I'm using. There is more functionality in the loop than I put in the example. – Sandu Chicu Jul 22 '23 at 14:08
  • If your function has many returns, add a wrapper first in the function: `struct s { std::atomic& b; ~s() { b = true; }} setter{threadFinished};` – Ted Lyngmo Jul 22 '23 at 14:09
  • Minor point: `if (threadFinished.load())` can be written `if (threadFinished)`. – Pete Becker Jul 22 '23 at 14:09

2 Answers2

2

The interface of std::future provides you with possibility to both obtain the result and checking if it's finished by inspecting results of wait member functions:

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

int main()
{
    std::packaged_task<void()> task{ []() {
        for(auto i = 0; i < 16; ++i) {
            std::this_thread::sleep_for(std::chrono::milliseconds{ 256 });
        }
        std::cout << "The task is done!\n";
    } };
    auto future = task.get_future();
    std::thread{ std::move(task) }.detach();
    while(future.wait_for(std::chrono::milliseconds{ 512 }) != std::future_status::ready) {
        std::cout << "Waiting for the result...\n";
    }
    return EXIT_SUCCESS;
}
The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
1

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.

huseyin tugrul buyukisik
  • 11,469
  • 4
  • 45
  • 97