简体   繁体   English

在 pthread_cancel 之后“终止调用而没有活动异常”

[英]"terminate called without an active exception" after pthread_cancel

In probing the conditions of this question , a problem arose, exemplified by the code below.在探究本题的条件时,出现了一个问题,如下代码所示。

#include <iostream>
#include <thread>
#include <chrono>
#include <stdexcept>
#include <cxxabi.h>

using namespace std;

// mocking external library call stuck in a strictly user-land infinite loop
int buggy_function_simulation()
{
    // cout << "In buggy function" << endl; // (1)
    int counter = 0;
    while (true)
    {
        if ( ++counter == 1000000 ) { counter = 0; }
    }
    return 0;
}

int main(int argc, char **argv) {
    cout << "Hello, world!" << endl;

    auto lambda = []() {
        pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, nullptr );
        // cout << "ID: "<<pthread_self() <<endl; // (2)
        try
        {
            cout << "ID: "<<pthread_self() <<endl; // (3)
            buggy_function_simulation();
        }
        catch ( abi::__forced_unwind& )
        {
            cout << "thread cancelled!" << endl; // (4)
            throw;
        }
    };

    std::thread th(lambda);

    pthread_t id = th.native_handle();
    cout << id << endl;

    this_thread::sleep_for(chrono::seconds(1));
    cout << "cancelling ID: "<< id << endl;

    pthread_cancel(id);
    th.join();

    cout << "cancelled: "<< id << endl;

    return 0;
}

Compiling and running results in an abort:编译运行结果中止:

$ g++ -g -Og -std=c++11 -pthread -o test test.cpp -lpthread
$ ./test
Hello, world!
139841296869120
ID: 139841296869120
cancelling ID: 139841296869120
terminate called without an active exception
Aborted (core dumped)
$

Note that the diagnostic output (4) does not appear.请注意,诊断输出 (4) 不会出现。

If I comment out (3) and uncomment (2), the result is:如果我注释掉 (3) 和取消注释 (2),结果是:

$ ./test
Hello, world!
139933357348608
ID: 139933357348608
cancelling ID: 139933357348608
cancelled: 139933357348608
$

Again, the output at (4) does not appear (why?), but the abort has been obviated.同样,(4) 处的输出没有出现(为什么?),但已避免中止。

If, alternately, I retain (3), leave (2) commented out, and uncomment (1), the result is finally as expected:或者,如果我保留 (3),将 (2) 注释掉,并取消注释 (1),结果最终如预期:

$ ./test
Hello, world!
139998901511936
ID: 139998901511936
In buggy function
cancelling ID: 139998901511936
thread cancelled!
cancelled: 139998901511936
$

So, the questions are:所以,问题是:

  • what is the reason for the "terminate called without an active exception" abort in the first case?在第一种情况下,“在没有活动异常的情况下终止调用”中止的原因是什么?
  • why is the catch block not activated in the second case?为什么在第二种情况下没有激活 catch 块?
  • why did uncommenting (1) in the third case make such a difference?为什么在第三种情况下取消注释 (1) 会产生如此大的不同?

For completeness, here is the stack trace from gdb for the first case:为了完整起见,这里是第一种情况下 gdb 的堆栈跟踪:

Program terminated with signal SIGABRT, Aborted.
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
51      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
[Current thread is 1 (Thread 0x7f5d9b49a700 (LWP 12130))]
(gdb) where
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007f5d9b879801 in __GI_abort () at abort.c:79
#2  0x00007f5d9bece957 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007f5d9bed4ab6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007f5d9bed4af1 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007f5d9bed44ba in __gxx_personality_v0 () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00007f5d9bc3a708 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#7  0x00007f5d9bc3acfc in _Unwind_ForcedUnwind () from /lib/x86_64-linux-gnu/libgcc_s.so.1
#8  0x00007f5d9c1dbf10 in __GI___pthread_unwind (buf=<optimized out>) at unwind.c:121
#9  0x00007f5d9c1d0d42 in __do_cancel () at ./pthreadP.h:297
#10 sigcancel_handler (sig=<optimized out>, si=0x7f5d9b499bb0, ctx=<optimized out>) at nptl-init.c:215
#11 <signal handler called>
#12 buggy_function_simulation () at test.cpp:15
#13 0x0000558865838227 in <lambda()>::operator() (__closure=<optimized out>) at test.cpp:29
#14 std::__invoke_impl<void, main(int, char**)::<lambda()> > (__f=...) at /usr/include/c++/7/bits/invoke.h:60
#15 std::__invoke<main(int, char**)::<lambda()> > (__fn=...) at /usr/include/c++/7/bits/invoke.h:95
#16 std::thread::_Invoker<std::tuple<main(int, char**)::<lambda()> > >::_M_invoke<0> (this=<optimized out>)
    at /usr/include/c++/7/thread:234
#17 std::thread::_Invoker<std::tuple<main(int, char**)::<lambda()> > >::operator() (this=<optimized out>)
    at /usr/include/c++/7/thread:243
#18 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main(int, char**)::<lambda()> > > >::_M_run(void) (
    this=<optimized out>) at /usr/include/c++/7/thread:186
#19 0x00007f5d9beff66f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#20 0x00007f5d9c1d26db in start_thread (arg=0x7f5d9b49a700) at pthread_create.c:463
#21 0x00007f5d9b95a88f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

That message can be triggered if you throw from inside a function marked noexcept .如果您从标记为noexcept的函数内部抛出,则可以触发该消息。 All destructors are implicitly noexcept , so if the thread is running a destructor when the exception triggered by pthread_cancel is thrown, your program will terminate and you will get that message.所有析构函数都是隐式的noexcept ,因此如果线程在pthread_cancel触发的异常被抛出时正在运行析构函数,您的程序将终止并且您将收到该消息。

operator<< for std::cout is a formatted output operation, which constructs a sentry object, which is destructed on exit (see https://en.cppreference.com/w/cpp/named_req/FormattedOutputFunction ). std::coutoperator<<是格式化输出操作,它构造一个sentry对象,该对象在退出时被破坏(参见https://en.cppreference.com/w/cpp/named_req/FormattedOutputFunction )。 If the cancel comes while the destructor of the sentry object is being processed, this will thus terminate your application.如果在处理sentry对象的析构函数时发生取消,这将终止您的应用程序。

Do not use PTHREAD_CANCEL_ASYNCHRONOUS in C++.不要在 C++ 中使用PTHREAD_CANCEL_ASYNCHRONOUS Even using pthread_cancel at all can be problematic due to the automatic rethrow from catch clauses.由于catch子句自动重新抛出,即使完全使用pthread_cancel也可能会出现问题。

UPDATE:更新:

pthread_cancel is a POSIX C function, intended to work with C code. pthread_cancel是一个 POSIX C 函数,旨在与 C 代码一起使用。 It has two modes of operation: synchronous and asynchronous.它有两种操作模式:同步和异步。

Synchronous use of pthread_cancel sets an internal flag on the target thread which is then check in certain functions marked as cancellation points in the POSIX documentation.同步使用pthread_cancel在目标线程上设置一个内部标志,然后检查在 POSIX 文档中标记为取消点的某些函数。 If any of those functions are called by the target thread, then cancellation is triggered.如果目标线程调用了这些函数中的任何一个,则会触发取消。 On Linux this is done by raising a special exception using the C++ exception mechanism that cannot be caught and discarded.在 Linux 上,这是通过使用无法捕获和丢弃的 C++ 异常机制引发特殊异常来完成的。 This triggers stack unwinding, calls C++ destructors, and runs code registered with pthread_cleanup_push .这会触发堆栈展开、调用 C++ 析构函数并运行使用pthread_cleanup_push注册的代码。 This is compatible with normal C++ code, assuming nothing tries to catch and discard the exception.这与正常的 C++ 代码兼容,假设没有尝试捕获和丢弃异常。 If all catch blocks rethrow, then everything works as expected.如果所有 catch 块都重新抛出,那么一切都会按预期进行。 If the cancellation starts inside a function marked noexcept (such as a destructor, which is noexcept by default), then the program will terminate.如果取消在标记为noexcept的函数(例如析构函数,默认情况下为noexcept )内部开始,则程序将终止。

Asynchronous use of pthread_cancel is different. pthread_cancel的异步使用是不同的。 This sends a special signal to the target thread which interrupts it at any arbitrary point and starts the stack unwinding process described above.这会向目标线程发送一个特殊信号,该信号会在任意点中断它并启动上述堆栈展开过程。 This is much more dangerous, as the code may be in the middle of evaluating any arbitrary expression, so the state of the application's data is much less well defined.这要危险得多,因为代码可能正在评估任意表达式,因此应用程序数据的状态定义得不太清楚。

If you use asynchronous cancellation with code that has been designed to support it, then this can be OK.如果您将异步取消与设计为支持它的代码一起使用,那么这就没问题。 It may be possible to make code async-cancel-safe through careful use of pthread_setcancelstate to disabled cancellation in specific regions, and use of pthread_cleanup_push to register cancellation cleanup handlers, but this cannot be done in all cases.通过仔细使用pthread_setcancelstate在特定区域禁用取消,并使用pthread_cleanup_push注册取消清理处理程序,可以使代码异步取消安全,但这并非在所有情况下都可以做到。

With synchronous cancellation, if a function declared noexcept does not call any cancellation point functions, then all is well.对于同步取消,如果声明为noexcept的函数不调用任何取消点函数,那么一切都很好。 With asynchronous cancellation, all code is a potential cancellation point, so before entering any code that is marked noexcept , you must call pthread_setcancelstate to temporarily disable cancellation, otherwise if the cancellation signal is received while that function is running then terminate will be called due to the cancellation exception.对于异步取消,所有代码都是潜在的取消点,因此在输入任何标记为noexcept的代码之前,您必须调用pthread_setcancelstate来暂时禁用取消,否则如果在该函数运行时收到取消信号,则terminate将被调用,因为取消异常。 As noted above, this includes all destructors which are not explicitly marked noexcept(false) .如上所述,这包括所有未明确标记为noexcept(false)的析构函数。

Consequently, any call to arbitrary C++ library code (which may therefore construct C++ objects with destructors) is a potential hazard when using asynchronous cancellation, and you must call pthread_setcancelstate to disable cancellation around any block of code which creates C++ objects with destructors, and/or calls into C++ library code out of your control (such as standard library functions).因此,在使用异步取消时,任何对任意 C++ 库代码的调用(因此可能会使用析构函数构造 C++ 对象)都是潜在的危险,并且您必须调用pthread_setcancelstate来禁用任何使用析构函数创建 C++ 对象的代码块周围的取消,和/或调用您无法控制的 C++ 库代码(例如标准库函数)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM