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

时间:2016-06-07 11:38:29

标签: 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 ;
}

只要不使用优化(或-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)上重现。

你可以重现这个吗?你有解释吗?这种行为似乎很奇怪(至少对我而言)。我很乐意收到某种反馈。谢谢!

2 个答案:

答案 0 :(得分:4)

在Linux上(与大多数操作系统一样),异常是与语言无关的功能,而pthread取消是使用与语言无关的异常实现的(参见例如Cancellation and C++ Exceptions)。

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

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

这受优化影响的原因是C ++个性是懒惰安装的,但是在更高的优化级别下,编译器可能会决定抢先安装C ++个性。通过运行C ++异常机制,即使在较低的优化级别,您也可以保证崩溃,例如与try { throw 0; } catch (int) {}

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

更加hacky和高度不支持的解决方案是确保所有异步取消点(即收到异步取消时取消的线程可能出现的所有指令)都是实际上由C ++ unwind表覆盖。首先,您需要使用-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 ;
}

答案 1 :(得分:-1)

  

有人在这里写道,当一个线程对象超出范围且处于可连接状态时,程序会中止。“

这就是39.3.1.3/1 [线程析构函数]实际上说的:

  

如果是joinable(),则调用std :: terminate()。 [...]因此,程序员必须确保在线程仍可连接时永远不会执行析构函数。