ara*_*yq2 5 c++ g++ pthreads c++11 stdthread
在探讨这个问题的条件时,出现了一个问题,以下面的代码为例。
#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;
}
Run Code Online (Sandbox Code Playgroud)
编译和运行导致中止:
$ 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)
$
Run Code Online (Sandbox Code Playgroud)
请注意,不会出现诊断输出 (4)。
如果我注释掉(3)并取消注释(2),结果是:
$ ./test
Hello, world!
139933357348608
ID: 139933357348608
cancelling ID: 139933357348608
cancelled: 139933357348608
$
Run Code Online (Sandbox Code Playgroud)
同样,(4) 处的输出没有出现(为什么?),但中止已被消除。
如果,交替,我保留(3),离开(2)注释掉,并取消注释(1),结果最终如预期:
$ ./test
Hello, world!
139998901511936
ID: 139998901511936
In buggy function
cancelling ID: 139998901511936
thread cancelled!
cancelled: 139998901511936
$
Run Code Online (Sandbox Code Playgroud)
所以,问题是:
为了完整起见,这里是第一种情况下来自 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
Run Code Online (Sandbox Code Playgroud)
如果您从标记为 的函数内部抛出该消息,则可以触发该消息noexcept。所有的析构函数都是隐式的noexcept,所以如果在引发由 触发的异常时线程正在运行析构函数pthread_cancel,您的程序将终止,您将收到该消息。
operator<<forstd::cout是格式化输出操作,它构造一个sentry对象,该对象在退出时被破坏(请参阅https://en.cppreference.com/w/cpp/named_req/FormattedOutputFunction)。如果在sentry处理对象的析构函数时取消,这将终止您的应用程序。
不要PTHREAD_CANCEL_ASYNCHRONOUS在 C++ 中使用。pthread_cancel由于从catch子句自动重新抛出,即使根本使用也可能有问题。
更新:
pthread_cancel是一个 POSIX C 函数,用于处理 C 代码。它有两种操作模式:同步和异步。
同步使用pthread_cancel在目标线程上设置一个内部标志,然后检查在POSIX 文档中标记为取消点的某些函数。如果目标线程调用了这些函数中的任何一个,则会触发取消。在 Linux 上,这是通过使用无法捕获和丢弃的 C++ 异常机制引发特殊异常来完成的。这会触发堆栈展开、调用 C++ 析构函数并运行使用pthread_cleanup_push. 这与普通的 C++ 代码兼容,假设没有尝试捕获和丢弃异常。如果所有 catch 块都重新抛出,那么一切都按预期进行。如果取消在标记的函数内开始noexcept(例如noexcept默认情况下的析构函数),则程序将终止。
异步使用pthread_cancel是不同的。这会向目标线程发送一个特殊信号,该信号会在任意点中断它并启动上述堆栈展开过程。这是更危险的,因为代码可能正在评估任何任意表达式的中间,因此应用程序数据的状态定义得不够明确。
如果您将异步取消与旨在支持它的代码一起使用,那么这可能没问题。通过pthread_setcancelstate在特定区域仔细使用to 禁用取消,并使用pthread_cleanup_push来注册取消清理处理程序,可以使代码异步取消安全,但这不能在所有情况下都完成。
使用同步取消,如果声明的函数noexcept不调用任何取消点函数,那么一切都很好。对于异步取消,所有代码都是一个潜在的取消点,因此在输入任何标记为 的代码之前noexcept,您必须调用pthread_setcancelstate以暂时禁用取消,否则如果在该函数运行时收到取消信号,terminate则会由于取消异常而被调用. 如上所述,这包括所有未明确标记的析构函数noexcept(false)。
因此,在使用异步取消时,对任意 C++ 库代码(因此可能会使用析构函数构造 C++ 对象)的任何调用都是潜在的危险,并且您必须调用pthread_setcancelstate以禁用使用析构函数创建 C++ 对象的任何代码块周围的取消,和/或调用不受您控制的 C++ 库代码(例如标准库函数)。
| 归档时间: |
|
| 查看次数: |
1191 次 |
| 最近记录: |