C++,set_terminate是否对每个线程都是本地的?

14

在C++ 2011或C++ 2003中,set_terminate/get_terminate是否应为几个线程设置不同的终止异常处理器?

例如,如果我有一个程序并将终止处理程序设置为func_1;然后我启动3个线程。新线程中的终止处理程序是什么?如果在每个线程中,我将终止处理程序分别设置为第一线程中的func_2,第二线程中的func_3等等。

N3242(C++ 2011草案)在[handler.functions][support.exception]/[exception.terminate]中没有任何关于此的说明。

PS:您可以回答C++2011或C++2003,也可以回答这些标准的任何流行实现。

附注:这里有一个针对此问题的FCD评论...C++ FCD Comment Status Rev. 5 N3249 (2011)

GB 71    18.6.2.4 / 18.8.2.2 / 18.8.3.2   

“std::set_new_handler()”,“std::set_unexpected()”和“std::set_terminate()”函数的线程安全性未指定,因此无法以线程安全的方式使用这些函数。 必须明确规定这些函数的线程安全保证,并提供新接口,使得可以以线程安全的方式查询和安装处理程序。 LWG 1365已经做了修改,接受了该建议。 请参阅文件N3189

1
在发布C++11之前,DR1365问题已经得到解决,采用了我回答中引用的关于数据竞争的语言。 - ecatmur
4个回答

10

17.6.4.7p4表示:

调用set_*get_*函数不应该产生数据竞争。对任何一个set_*函数的调用都应该与对同一set_*函数和对应的get_*函数后续调用进行同步。

这强烈暗示了即使在不同线程中调用时,set_*get_*函数也在操作同一全局状态。18.8.3下的所有段落都讨论“当前处理函数”,没有提到其他关于线程的内容;这表明处理函数是整个程序的属性;同样,17.6.4.7有:

2 - C++程序可以在执行过程中安装不同的处理函数[...]
3 - C++程序可以通过调用以下函数来获取指向当前处理函数的指针[...]

这些段落在程序上下文中讨论当前处理函数,表明它是程序范围而不是线程本地的。


当 set_* 和 get_* 在线程本地状态上运行时,也不会有数据竞争。而且至少有三种实现文档化的线程本地状态:ms、ibm、sun。 - osgx
1
有MS VS 6和SUN(某些老版本):http://msdn.microsoft.com/en-us/library/aa272914(v=vs.60).aspx和http://www.amath.unc.edu/sysadmin/DOC4.0/c-plusplus/c%2B%2B_ug/Exception_Handling.doc.html;两者都提到了线程本地的`terminate`:MS:“在多线程环境中,终止函数是针对每个线程单独维护的。”;Sun:“每个线程可以设置自己的terminate()或unexpected()函数。在一个线程中调用set_terminate()或set_unexpected()仅影响该线程中的异常。”它们错了吗? - osgx
1
@osgx MS文档在讨论<eh.h>中的C库函数; C++库函数记录在http://msdn.microsoft.com/en-us/library/aa241196(v=vs.60).aspx中。正如你所说,Sun文档非常老旧; 在C++11之前,C++不考虑线程,因此拥有线程本地终止处理程序将是一种符合规范的扩展。 - ecatmur
<eh.h> set_terminate()函数的最新MSDN页面链接:http://msdn.microsoft.com/en-us/library/t6fk7h29.aspx(这样就不会被15年前的实现所分心)。虽然此函数是C运行时的一部分,但文档中说“set_terminate与C++异常处理一起使用...”,因此可能是C++运行时使用它来实现其来自`<exception>set_terminate()。另一方面,<exception>set_terminate()`的MSDN文档-http://msdn.microsoft.com/en-us/library/ycf93beb.aspx-没有提到线程。 - Michael Burr

4
在标准中,18.8.3.2 set_terminate [set.terminate]中写道: terminate_handler set_terminate(terminate_handler f) noexcept;

1 效果:将由f指定的函数建立为终止异常处理的当前处理程序函数。

[[noreturn]] void terminate() noexcept;

2 效果:调用当前的terminate_handler函数。[注意:在这种情况下,默认的terminate_handler始终被认为是可调用的处理程序。—end note]

你可以看到,terminate()调用当前的terminate处理程序,而在set_handler部分中,它非常清楚地说明了它用于终止进程。当所有其他异常处理都失败时,无论从哪个线程运行,都会调用此处理程序。
只有一个terminate处理程序,并且它总是从程序终止的位置调用。

IBM表示:“您可以通过提供调用pthread_exit()作为终止函数的函数来覆盖此行为,从而实现线程级别的终止。这将终止线程但不会终止进程。” - osgx
而终止“异常处理”并不意味着进程的终止。单个线程也可以被终止。 - osgx
但这是全局终止处理程序,它引用了调用它的线程(通过调用pthread_exit),而不是线程特定的处理程序。 - Daniel Frey
2
@osgx 在这种情况下,pthread_exit仍然是程序范围的终止处理程序;IBM只是提供了一个扩展,允许终止处理程序仅终止线程。 - ecatmur

4
标准并没有明确规定;[set.terminate] 只说明了当前异常处理程序的功能。

[...] 用于终止异常处理的当前处理程序函数。

但是它没有说明“当前”是全局的还是每个线程的。因此,它取决于实现。
例如,在 MSVC++ 中: https://msdn.microsoft.com/en-us/library/t6fk7h29.aspx

在多线程环境中,终止函数针对每个线程分别维护。每个新线程都需要安装自己的终止函数。因此,每个线程负责自己的终止处理。


1
"C2003没有线程,任何线程支持都是供应商扩展,因此只有供应商提供的文档才能提供答案。如果处理程序是每个线程的,则文档应该说明。据我所知,没有任何实现这样做。C++2011对终止处理程序的线程特性没有任何说明。在C++11中,将其保持为每个线程几乎没有意义,因为您无法杀死一个线程(请查阅Google kill+thread+c++11)。因此,无论您做什么,程序都必须终止。看起来,根据请求终止程序的线程有不同的方式来终止程序并不是任何人需要的功能。"

Sun/Oracle在(Solaris Studio 12.2)中实现了线程局部的set_terminate:“每个线程都可以设置自己的terminate()或unexpected()函数。在一个线程中调用set_terminate()或set_unexpected()仅影响该线程中的异常。” - osgx
在MSVC中,它也是线程特定的。不同的线程由代码的不同区域或第三方库出于不同的原因启动。在终止进程之前进行一些线程特定的活动是合理的。 - asynchronos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接