当 main() 退出时,分离的线程会发生什么?

2021-12-06 00:00:00 multithreading exit c++ c++11 stdthread

假设我正在启动一个 std::thread 然后 detach() 它,所以即使 std::thread 曾经代表它,超出了范围.

Assume I'm starting a std::thread and then detach() it, so the thread continues executing even though the std::thread that once represented it, goes out of scope.

进一步假设程序没有可靠的加入分离线程的协议1,所以当main()退出时分离线程仍然运行.

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

我在标准中找不到任何内容(更准确地说,在 N3797 C++14 草案中),它描述了应该发生的情况,1.10 和 30.3 都没有包含相关的措辞.

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 另一个可能等效的问题是:分离的线程是否可以再次加入",因为无论您发明要加入的协议,信令部分都必须在线程仍在运行,操作系统调度程序可能会在信号刚执行完后决定让线程休眠一个小时,而接收端无法可靠地检测到线程实际上已完成.

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.

如果在分离线程运行时用完 main() 是未定义的行为,那么 any 使用 std::thread::detach() 是未定义的行为,除非主线程永远不会退出2.

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

因此,在运行分离线程的情况下用完 main() 必须具有定义效果.问题是:哪里(在 C++ 标准中,不是 POSIX,不是 OS 文档,...)是那些定义的效果.

Thus, running out of main() 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 一个分离的线程不能被加入(在std::thread::join()的意义上).您可以等待来自分离线程的结果(例如通过来自std::packaged_task的未来,或者通过计数信号量或标志和条件变量),但这不会不保证线程已经完成执行.实际上,除非您将信号部分放入线程的第一个自动对象的析构函数中,否则通常将是在信号之后运行的代码(析构函数)代码.如果操作系统安排主线程在分离的线程运行完上述析构函数之前使用结果并退出,那么 ^Wi 定义会发生什么?

2 A detached thread cannot be joined (in the sense of std::thread::join()). You can wait for results from detached threads (e.g. via a future from std::packaged_task, 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?

推荐答案

原问题main() 退出时分离的线程会发生什么"的答案是:

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

它会继续运行(因为标准没有说它已停止),而且这是明确定义的,只要它既不涉及其他线程的 (automatic|thread_local) 变量也不涉及静态对象.

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.

这似乎允许线程管理器作为静态对象([basic.start.term]/4 中的注释说明了这一点,感谢@dyp 提供了指针).

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

当静态对象的销毁完成时会出现问题,因为然后执行进入一个只能执行信号处理程序中允许的代码的状态([basic.start.term]/1, 1st sentence).在 C++ 标准库中,只有 库([support.runtime]/9, 2nd sentence).特别是,一般来说,排除 condition_variable(它是实现定义的,是否保存以在信号处理程序中使用,因为它不是 <原子>).

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:

是的,使用 *_at_thread_exit 系列函数(notify_all_at_thread_exit()std::promise::set_value_at_thread_exit()、...).

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

正如问题的脚注 [2] 中所指出的,向条件变量或信号量或原子计数器发出信号不足以加入分离的线程(在确保其执行结束的意义上有-发生在等待线程接收到所述信号之前),因为一般来说,在例如之后会执行更多的代码条件变量的notify_all(),特别是自动和线程局部对象的析构函数.

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.

在线程做的最后一件事(在自动和线程局部对象的析构函数已经发生之后)运行信号是_at_thread_exitcode> 函数系列的设计目标.

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.

因此,为了避免在没有超出标准要求的任何实现保证的情况下出现未定义的行为,您需要(手动)将分离的线程与 _at_thread_exit 函数一起执行信号或使分离的线程只执行对信号处理程序也是安全的代码.

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.

相关文章