在多线程 C++11 程序中未处理异常时会发生啥?

Posted

技术标签:

【中文标题】在多线程 C++11 程序中未处理异常时会发生啥?【英文标题】:What happens when an exception goes unhandled in a multithreaded C++11 program?在多线程 C++11 程序中未处理异常时会发生什么? 【发布时间】:2011-09-01 14:50:09 【问题描述】:

如果我有一个运行两个线程的 C++11 程序,其中一个抛出未处理的异常,会发生什么?整个节目会不会死得一塌糊涂?抛出异常的线程会单独死掉吗(如果是这样,在这种情况下我可以获得异常吗)?完全不同的东西?

【问题讨论】:

很确定线程会死掉,如果不将其捕获在线程中,您将无法“处理”它......但我认为其他线程将继续。不过很想听到权威的答案。 @sje397:我当然不会打赌这个过程会继续存在。我想知道如果使用exception_ptr,我们是否可以希望异常能够跨线程传播,会很好。 看来我错了。好问题。 @Matthieu:在跨线程调用期间编组到另一个线程的异常不会被视为“未处理”,IMO。 【参考方案1】:

什么都没有真正改变。 n3290 中的写法是:

如果没有找到匹配的处理程序,则调用函数std::terminate()

terminate 的行为可以用set_terminate 自定义,但是:

必需的行为terminate_handler 应终止程序的执行而不返回调用者。

所以这种情况下程序退出,其他线程无法继续运行。

【讨论】:

std::exception_ptr 允许将异常从一个线程迁移到另一个线程,这种传播是否自动化,以便在调用线程中引发异常? (可以说,这可能很困难) @Kerrek 不,您绝对不能在其“线程或来源”之外捕获异常。异常捕获取决于堆栈展开,而堆栈基本上是特定于线程的。 在 C++03 中(线程是否超出标准)与 pthreads。未捕获的主线程中的异常会杀死应用程序(正常)。但是子线程中的异常只会杀死该线程。我很惊讶这种行为不是新标准所说的应该实施的。 @Branko:对于 C++0x 期货,异常肯定应该与结果一起传输。这意味着必须先在工作线程中展开展开(因为工作线程甚至可能退出),然后才转移异常。 @Kerrek:阅读第 30.6 节,其中讨论了“期货”并描述了结果(包括异常)是如何传播的。【参考方案2】:

由于似乎对异常传播有正当的兴趣,并且这与问题略有相关,因此我的建议是:std::thread 被认为是构建不安全的原语,例如更高层次的抽象。它们在异常方面是双重风险的:如果异常在我们刚刚启动的线程内发生,那么一切都会爆炸,正如我们所展示的那样。但是如果在启动std::thread 的线程中出现异常,我们可能会遇到麻烦,因为std::thread 的析构函数要求*this 要么加入要么分离(或者等效地,not-a-线程)。违反这些要求会导致...调用std::terminate

std::thread的危险代码图:

auto run = []

    // if an exception escapes here std::terminate is called
;
std::thread thread(run);

// notice that we do not detach the thread
// if an exception escapes here std::terminate is called

thread.join();
// end of scope

当然,有些人可能会争辩说,如果我们只是 detached 我们启动的每个线程,我们在第二点上是安全的。问题在于,在某些情况下,join 是最明智的做法。例如,快速排序的“幼稚”并行化需要等到子任务结束。在这些情况下,join 用作同步原语(集合点)。

幸运的是,我提到的那些更高级别的抽象确实存在并且随标准库一起提供。它们是std::asyncstd::future 以及std::packaged_taskstd::promisestd::exception_ptr。上述等效的异常安全版本:

auto run = []() -> T // T may be void as above

    // may throw
    return /* some T */;
;

auto launched = std::async(run);
// launched has type std::future<T>

// may throw here; nothing bad happens

// expression has type T and may throw
// will throw whatever was originally thrown in run
launched.get();

实际上,您可以将责任推给另一个线程,而不是在调用 async 的线程中调用 get

// only one call to get allowed per std::future<T> so
// this replaces the previous call to get
auto handle = [](std::future<T> future)

    // get either the value returned by run
    // or the exception it threw
    future.get();
;

// std::future is move-only
std::async(handle, std::move(launched));
// we didn't name and use the return of std::async
// because we don't have to

【讨论】:

+1 绝对同意将std::thread 视为不安全的原语。

以上是关于在多线程 C++11 程序中未处理异常时会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

当 .NET 线程抛出异常时会发生啥?

如果我在多线程服务器程序中运行并行代码会发生啥? [复制]

当套接字连续接收到超出程序处理能力的数据时会发生啥?

JVM 终止时会发生啥?

为啥后台线程中未处理的异常不会使应用程序域崩溃?

当事件触发并尝试在不再存在的对象中执行事件处理程序时会发生啥?