致命:在 C++ 中不会重新抛出异常

Tej*_*ula 4 c++ linux multithreading

我试图了解 pthread_cancel 在 c++ 中的 Linux 环境中的用法。但我遇到了运行时问题。

class A {
    public:
        A(){cout<<"constructor\n";}
        ~A(){cout<<"destructor\n";}
};
void* run(void* data) {
    A a;
    while(1) {
        //sleep(1);
        cout<<"while\n";
    }
}
int main() {
    pthread_t pid;
    pthread_create(&pid,NULL,run,NULL);
    sleep(2);;
    pthread_cancel(pid);
    cout<<"Canceled\n";
    pthread_exit(0);
}
Run Code Online (Sandbox Code Playgroud)

输出:

constructor 
while 
while 
...
while 
while 
Canceled 
FATAL: exception not rethrown 
Aborted (core dumped)
Run Code Online (Sandbox Code Playgroud)

核心文件分析:

(gdb) where
#0  0x00000036e8c30265 in raise () from /lib64/libc.so.6
#1  0x00000036e8c31d10 in abort () from /lib64/libc.so.6
#2  0x00000036e9c0d221 in unwind_cleanup () from /lib64/libpthread.so.0
#3  0x00000036fa69042b in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) () from /usr/lib64/libstdc++.so.6
#4  0x00000000004009c5 in run(void*) ()
#5  0x00000036e9c0677d in start_thread () from /lib64/libpthread.so.0
#6  0x00000036e8cd49ad in clone () from /lib64/libc.so.6
Run Code Online (Sandbox Code Playgroud)

但是,如果我取消注释线程函数运行中的 sleep(1),我将得到以下输出。

constructor 
while 
Canceled 
destructor
Run Code Online (Sandbox Code Playgroud)

您能否解释一下为什么程序在第一种情况下给出“致命:异常未重新抛出”而不是在第二种情况下给出?请举例详细解释为什么pthread_cancel比pthread_kill更安全?

Ste*_*sen 5

抱歉要告诉大家一个坏消息,但是在 Linux 的 C++ 中让 pthread_cancel 正常工作是非常困难的。

原因是 Linux 中的 pthread_cancel 方法实现绝对是可怕的。特别是它会导致抛出异常(abi::__forced_unwind截至我撰写本文时)。这在 C 程序中可能没问题,但在 C++ 中,它使得正确处理线程取消变得非常困难。特别是,如果您有任何处理异常的代码(例如catch (...)),则此内部异常将被处理,并且不会按需要的方式向上传递到堆栈。在这种情况下,您将看到您所看到的错误消息。

也就是说,我不知道你的代码中的什么会“吃掉”这个异常,但我怀疑正是这种性质导致了问题。

在您的特定示例中,当您想要退出而不调用 pthread_cancel 时,可以通过使用标志或类似的注释来轻松解决此问题。不幸的是,这使得sleep更加复杂(您需要以较小的间隔多次调用它才能检查标志)并且调用accept几乎不可能(您必须从另一个线程或进程连接到端口以强制accept退出,这样您就可以检查变量)。一般来说,关于线程取消的问题有一个很长的讨论(https://lwn.net/Articles/683118/),但考虑到 macOS(可能还有其他非 Linux“unix”)似乎处理得很好,我真的认为Linux的实现可以做得更好。但在此之前,pthread_cancel 仍然很难使用。