我有一个退出处理程序线程,等待工作线程完成其工作的条件。信令是从工作线程的析构函数完成的。
以下是退出处理程序线程的代码。
void Class::TaskExitHandler::run() throw()
{
while( ! isInterrupted() ) {
_book->_eot_cond.wait(); // Waiting on this condition
{
CLASS_NAMESPACE::Guard<CLASS_NAMESPACE::FastLock> eguard(_book->_exitlist_lock);
list<TaskGroupExecutor*>::const_iterator itr = _book->_exited_tasks.begin();
for( ; itr != _book->_exited_tasks.end(); itr++ ) {
(*itr)->join();
TRACER(TRC_DEBUG)<< "Deleting exited task:" << (*itr)->getLoc() << ":"
<< (*itr)->getTestID() << ":" << (*itr)->getReportName() << endl;
delete (*itr);
}
_book->_exited_tasks.clear();
}
_book->executeAny();
}
}
}
现在,观察到的是当工作线程捕获任何异常(从较低层引发)时,该线程将继续,并立即使用退出代码134(即SIGABRT)进行核心。
堆栈跟踪如下 -
#0 0x0000005555f49b4c in raise () from /lib64/libc.so.6
#1 0x0000005555f4b568 in abort () from /lib64/libc.so.6
#2 0x0000005555d848b4 in __gnu_cxx::__verbose_terminate_handler () from /usr/lib64/libstdc++.so.6
#3 0x0000005555d82210 in ?? () from /usr/lib64/libstdc++.so.6
#4 0x0000005555d82258 in std::terminate () from /usr/lib64/libstdc++.so.6
#5 0x0000005555d82278 in ?? () from /usr/lib64/libstdc++.so.6
#6 0x0000005555d81b18 in __cxa_call_unexpected () from /usr/lib64/libstdc++.so.6
#7 0x0000000120047898 in Class::TaskExitHandler::run ()
#8 0x000000012001cd38 in commutil::ThreadBase::thread_proxy ()
#9 0x0000005555c6e438 in start_thread () from /lib64/libpthread.so.0
#10 0x0000005555feed6c in __thread_start () from /lib64/libc.so.6
Backtrace stopped: frame did not save the PC
所以似乎这个run()函数指定它不会使用“throw()”规范抛出任何异常,引发异常(来自第4帧)。根据有关__cxa_call_unexpected()的各种引用,stacktrace描述了在具有“throw()”规范的函数中引发异常时编译器要中止的典型行为。 我对这个问题的分析是对的吗?
为了测试,我在这个方法中添加了一个try catch,并打印了异常消息。现在这个过程没有核心。异常消息与工作线程捕获的异常消息相同。 我的问题是,这个线程如何访问另一个捕获的异常?他们是否共享一些与异常处理相关的数据结构?
请对此有所了解。这真是令人费解......
注意: - 根据stacktrace,调用run()后立即引发call_unexpected。这加强了我对某种异常堆栈或数据共享的怀疑。但没有找到任何对此行为的引用。
答案 0 :(得分:1)
我将回答我自己的问题。 在这种情况下发生的事情是在TaskExitHandler线程中调用了析构函数。这个析构函数执行的操作相同,导致主线程中出现异常。
由于TaskExitHandler线程被设计为不抛出(或者更确切地说是预期的),因此没有try-catch块,因此在引发异常时进程中止。
由于析构函数的调用是隐式的,它从不在stacktrace中显示,因此很难找到它。必须跟踪每个对象以发现此异常泄漏。
感谢大家的积极参与:)这是我第一个得到积极回应的问题..
答案 1 :(得分:0)
我会采取刺激措施 - 希望这会让你足够继续你的研究。
我怀疑运行TaskExitHandler的线程是所有工作线程的父线程。 TEH将有一段艰难的时间与孩子们联系。
子/工作线程不处理抛出的异常。但是,必须在某处处理异常,否则整个过程将被关闭。父线程(也称为TEH)是进程的堆栈/链中用于处理异常的最后一站。您的示例代码显示TEH的异常处理是简单地抛出/不处理异常。所以它核心了。
它不一定是共享的数据结构,而是进程/线程ID和内存空间。子线程与父节点和彼此共享全局内存/堆空间,因此需要信号量和/或互斥锁用于锁定目的。
良好的封装要求工作线程应足够智能,以处理他们可能看到的任何/所有异常。这样,可以杀死单个工作线程,而不是关闭父线程和进程树的其余部分。 OTW,你可以继续在TEH中捕获异常,但是线程不太可能(或者应该)了解如何处理异常。
如果上述内容不明确,请添加评论,我很乐意进一步解释。
我做了一些研究并确认了针对堆内存而不是堆栈内存生成异常。进程的所有线程共享相同的堆*,因此当子线程没有捕获它时,为什么父线程会看到异常更有意义(至少对我而言)。 * FWIW,如果你分叉你的进程而不是开始一个新的线程,你也会得到一个新的堆。但是,对于内存而言,分叉是一项昂贵的操作,因为您将所有堆内容复制到新进程中。
这个SO线程讨论了设置一个线程来捕获所有异常,这可能是有意义的: catching exceptions from another thread