Home > Back-end >  How to join a number of threads which don't stop in C
How to join a number of threads which don't stop in C

Time:03-17

In C I have an std::vector of threads, each running a function running forever [while(true)]. I'm joining them in a for loop:

for (auto& thread : threads) 
{
    thread.join();
}

When the program finishes I'm getting a std::terminate() call inside the destructor of one of the threads. I think I understand why that happens, except for the first thread the other join calls don't get called.

What is the correct way of joining those threads? And is it actually necessary to join them? (assuming they are not supposed to join under normal circumstances)

CodePudding user response:

If the threads cannot be joined because they never exit then you could use std::thread::detach (https://en.cppreference.com/w/cpp/thread/thread/detach). Either way before joining you should always check std::thread::joinable (https://en.cppreference.com/w/cpp/thread/thread/joinable).

The std::terminate is indeed most likely due to a running thread being destroyed and not being detached or joined before that. Note however that what happens to detached threads on application exit is implementation defined. If possible you should probably change the logic in those threads to allow graceful exit (std::jthread or std::atomic could help make stoppable threads):

EDIT: Semi-complete C 17 "correct" code:

std::atomic stop{false};
std::vector<std::thread> threads;
threads.emplace_back(std::thread{[&] { while (!stop.load()) { /* */ }}});
threads.emplace_back(std::thread{[&] { while (!stop.load()) { /* */ }}});

//...

stop.store(true);

for (auto& thread : threads) 
{
    if (thread.joinable())
    {
        thread.join();
    }
}

Semi-complete C 20 "correct" code:

std::vector<std::jthread> threads;
threads.emplace_back(std::jthread{[] (std::stop_token stopToken) { while (!stopToken.stop_requested()) { /* */ }}});
threads.emplace_back(std::jthread{[] (std::stop_token stopToken) { while (!stopToken.stop_requested()) { /* */ }}});

The C 20 std::jthread allows functions that take std::stop_token to receive a signal to stop. The destructor std::~jthread() first requests stop via the token and then joins so in the above setup basically no manual cleanup is necessary. Unfortunately only MSVC STL and libstdc currently support it while Clang's libc does not. But it is easy enough to implement yourself atop of std::thread if you'd fancy a bit of exercise.

CodePudding user response:

What is the correct way of joining those threads?

Your way is fine, depending on what you're trying to do.

And is it actually necessary to join them?

Yes. And no.

See, the main issue with std::thread is that you need to clean them up or they'll "do bad things" (TM), but joining them is only one way of cleaning them up. The other way is to simply detach them from your actual threads, if you don't care to control them anymore (which seems to be the case?).

The things you need to ask yourself is if your setup makes sense, where you create a whole bunch of threads that don't end cleanly but instead are interrupted randomly by your entire process dying. What happens to the work they were supposed to do? If they write their output somewhere and it's interrupted half way through, are you, your employers and your customers okay with file corruption?

  • Related