C++:在没有活动异常(GCC)的情况下终止被调用

mik*_*o77 10 c++ multithreading gcc exception-handling

考虑以下程序:

#include <iostream>
#include <pthread.h>
#include <stdexcept>
#include <unistd.h>

static void* busy(void*)
{
  int oldstate ;
  auto result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldstate) ;
  if (result != 0)
#ifdef NOEXCEPT
    { std::cerr << "pthread_setcanceltype" << std::endl ; abort() ; }
#else
    throw std::runtime_error("pthread_setcanceltype") ;
#endif
  while (true) 
    ;
  return nullptr ;
}

static pthread_t start()
{
  pthread_t t ;
  int result = pthread_create(&t,nullptr,busy,nullptr) ;
  if (result != 0)
    throw std::runtime_error("pthread_create") ;
  return t ;
}

static void terminate(pthread_t t)
{
  auto result = pthread_cancel(t) ;
  if (result != 0) 
    throw std::runtime_error("pthread_cancel()") ;
  result = pthread_join(t,nullptr) ;
  if (result != 0) 
    throw std::runtime_error("pthread_join()") ;
}

int main()
{
  auto t = start() ;
  sleep(1) ; // may not cause an abort otherwise
  terminate(t) ;
  return 0 ;
}
Run Code Online (Sandbox Code Playgroud)

只要没有使用优化(或-O1),这就运行良好,例如使用g ++ -std = c ++ 11 -Wall -o test test.cc -pthread

但是,使用-O2或-O3时,程序将使用上面的消息中止.

也有点有趣:它通过-DNOEXCEPT编译运行.所以看来,如果一个线程在一个可能[sic!]抛出异常的函数中被取消,并且如果打开了优化,程序可能会中止. - 我看不出任何阻止​​这种情况的方法.

这对我来说可以在amd64 gcc 4.8.4(Ubuntu 14.04.3)和armv7l gcc 4.9.2(Raspbian 4.9.2-10)上重现.

你能复制一下吗?你有解释吗?这种行为似乎很奇怪(至少对我而言).我很乐意收到某种反馈.谢谢!

eca*_*mur 5

在 Linux(在大多数操作系统上)异常是一种语言无关的特性,并且 pthread 取消是使用与语言无关的异常来实现的(参见例如Cancellation 和 C++ Exceptions)。

当 pthread 取消传递到线程时(使用信号,但您不需要知道),展开机制会调用所有已安装的特性,以便它们可以在线程退出之前执行特定于语言的清理。(这很酷;这意味着在上面的文章中,您可以插入一个 catch 块abi::__forced_unwind来检测 - 尽管不是为了防止 - 线程取消。)

问题是异步取消可以发生在任何指令上,并且由 g++ 生成的 C++ 异常表只处理发生在已知能够生成异常的指令处的异常(即,但不仅是调用异常抛出函数)。如果在 C++ 表未涵盖的点生成异常,则 C++ 特性会恐慌并终止进程(因此“在没有活动异常的情况下调用终止”)。

受优化影响的原因是 C++ 特性是惰性安装的,但如果优化级别更高,编译器可能会决定抢先安装 C++ 特性。您可以通过使用 C++ 异常机制(例如使用try { throw 0; } catch (int) {}.

最简单的解决方法是确保在要异步取消的线程中未安装 C++ 特性。您可以通过将线程函数编译为 C 并且不从中调用任何 C++ 函数来确保这一点。

一个更棘手且高度不受支持的解决方案是确保所有异步取消点(即,接收异步取消时被取消线程可能所在的所有指令)实际上都被 C++ 展开表覆盖。首先你需要编译-fnon-call-exceptions; 其次,您必须确保可能是异步取消点的每条指令都已知为同步取消点的两个点之间,例如pthread_testcancel

static void* busy(void*)
{
  int oldstate ;
  auto result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldstate) ;
  if (result != 0)
#ifdef NOEXCEPT
    { std::cerr << "pthread_setcanceltype" << std::endl ; abort() ; }
#else
    throw std::runtime_error("pthread_setcanceltype") ;
#endif
  pthread_testcancel();
  for (unsigned i = 1; ; ++i) 
    if (i == 0)
      pthread_testcancel();
  return nullptr ;
}
Run Code Online (Sandbox Code Playgroud)