关于join线程中未捕获异常触发std::terminate,而detach线程不触发的技术疑问
嘿,这个问题问得太精准了,刚好戳中了C++线程模型里一个容易被忽略的设计细节!咱们一步步捋清楚:
先说说join()的情况
当你调用线程的join()方法时,主线程会阻塞等待这个子线程完全执行完毕。这里的关键是:C++里每个线程都有自己独立的执行上下文和调用栈,子线程里抛出的未捕获异常,是绝对没办法传播到主线程的——主线程的try-catch只能抓自己线程栈里的异常,根本碰不到子线程的栈帧。
那子线程跑完了,却带着一个未处理的异常,这时候C++标准怎么规定的?直接触发std::terminate()终止整个程序!因为这种未捕获的异常是一个严重的错误状态,没有任何代码能处理它,程序已经处于不可靠的状态了,只能强制终止。
看你给出的这段代码:
#include <thread> #include <print> void foo(bool throws) { if (throws) { throw std::runtime_error("Asked to throw"); } } int main() { try { std::thread t1(foo, false); std::thread t2(foo, true); t1.join(); t2.join(); } catch (std::exception const &e) { std::println("Failed with exception: {}", e.what()); } }
t2抛出了异常,但主线程的try-catch完全抓不到——因为这个异常在t2自己的线程栈里。当主线程调用t2.join()时,它只是等t2结束,却没法接收这个异常,所以符合标准里的触发条件,直接调用std::terminate()。
再聊聊detach()的真相
你换成detach()后程序返回0,其实是表象迷惑了你!
当你调用detach(),子线程就和主线程解绑了,变成了后台线程——这时候主线程完全不管它的死活,自己执行完main函数就直接退出进程了,进程一退出,所有后台线程都会被强制终止。
那如果detach的线程抛出未捕获异常,会不会触发std::terminate()?会的!但前提是这个异常抛出的时候,进程还在运行。比如你在main函数末尾加一行等待代码:
std::this_thread::sleep_for(std::chrono::seconds(1));
这时候主线程不会立刻退出,detach的t2有足够时间抛出异常,你就会看到程序还是会触发std::terminate()。
你之前看到的返回0,只是因为主线程跑得太快,进程在t2抛出异常之前就已经终止了,所以没机会触发std::terminate()而已。
额外提一句:怎么正确处理线程里的异常?
如果想让主线程能处理子线程的异常,有两个常用办法:
- 直接在子线程内部捕获异常,处理掉或者把异常信息通过某种方式(比如全局变量、回调、
std::promise)传递给主线程 - 用
std::packaged_task包装线程函数,把异常打包成std::future的结果,主线程通过future.get()来获取(如果有异常,get()会重新抛出这个异常,主线程就能捕获了)
内容来源于stack exchange

