Marc Mutz - mmutz Marc Mutz - mmutz - 1 year ago 140
C++ Question

What happens to a detached thread when main() exits?

Assume I'm starting a

and then
it, so the thread continues executing even though the
that once represented it, goes out of scope.

Assume further that the program does not have a reliable protocol for joining the detached thread1, so the detached thread still runs when

I cannot find anything in the standard (more precisely, in the N3797 C++14 draft), which describes what should happen, neither 1.10 nor 30.3 contain pertinent wording.

1 Another, probably equivalent, question is: "can a detached thread ever be joined again", because whatever protocol you're inventing to join, the signalling part would have to be done while the thread was still running, and the OS scheduler might decide to put the thread to sleep for an hour just after signalling was performed with no way for the receiving end to reliably detect that the thread actually finished.

If running out of
with detached threads running is undefined behaviour, then any use of
is undefined behaviour unless the main thread never exits2.

Thus, running out of
with detached threads running must have defined effects. The question is: where (in the C++ standard, not POSIX, not OS docs, ...) are those effects defined.

2 A detached thread cannot be joined (in the sense of
). You can wait for results from detached threads (e.g. via a future from
, or by a counting semaphore or a flag and a condition variable), but that doesn't guarantee that the thread has finished executing. Indeed, unless you put the signalling part into the destructor of the first automatic object of the thread, there will, in general, be code (destructors) that run after the signalling code. If the OS schedules the main thread to consume the result and exit before the detached thread finishes running said destructors, what will^Wis defined to happen?

Answer Source

The answer to the original question "what happens to a detached thread when main() exits" is:

It continues running (because the standard doesn't say it is stopped), and that's well-defined, as long as it touches neither (automatic|thread_local) variables of other threads nor static objects.

This appears to be allowed to allow thread managers as static objects (note in [basic.start.term]/4 says as much, thanks to @dyp for the pointer).

Problems arise when the destruction of static objects has finished, because then execution enters a regime where only code allowed in signal handlers may execute ([basic.start.term]/1, 1st sentence). Of the C++ standard library, that is only the <atomic> library ([support.runtime]/9, 2nd sentence). In particular, that—in general—excludes condition_variable (it's implementation-defined whether that is save to use in a signal handler, because it's not part of <atomic>).

Unless you've unwound your stack at this point, it's hard to see how to avoid undefined behaviour.

The answer to the second question "can detached threads ever be joined again" is:

Yes, with the *_at_thread_exit family of functions (notify_all_at_thread_exit(), std::promise::set_value_at_thread_exit(), ...).

As noted in footnote [2] of the question, signalling a condition variable or a semaphore or an atomic counter is not sufficient to join a detached thread (in the sense of ensuring that the end of its execution has-happened-before the receiving of said signalling by a waiting thread), because, in general, there will be more code executed after e.g. a notify_all() of a condition variable, in particular the destructors of automatic and thread-local objects.

Running the signalling as the last thing the thread does (after destructors of automatic and thread-local objects has-happened) is what the _at_thread_exit family of functions was designed for.

So, in order to avoid undefined behaviour in the absence of any implementation guarantees above what the standard requires, you need to (manually) join a detached thread with an _at_thread_exit function doing the signalling or make the detached thread execute only code that would be safe for a signal handler, too.