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)
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.