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.