我怎样才能传播线程之间的exception?
我们有一个单线程调用的函数(我们将其命名为主线程)。 在函数体内,我们产生了多个工作线程来完成CPU密集型工作,等待所有线程完成,然后在主线程上返回结果。
结果是调用者可以天真地使用这个函数,并且在内部它将使用多个核心。
迄今为止都很好..
我们面临的问题是处理例外。 我们不希望工作线程上的exception使应用程序崩溃。 我们希望调用者的函数能够在主线程上捕获它们。 我们必须在工作线程上捕获exception,并将它们传播到主线程,让它们从这里继续展开。
我们怎么做到这一点?
我能想到的最好的是:
- 在我们的工作线程(std :: exception和我们自己的几个)上捕获各种exception。
- loggingexception的types和消息。
- 在主线程上有一个相应的switch语句,它重新引发工作线程上logging的任何types的exception。
这具有明显的缺点,即只支持一组有限的exceptiontypes,并且每当添加新的exceptiontypes时都需要修改。
C ++ 11引入了允许在线程之间传输exception的exception_ptrtypes:
#include<iostream> #include<thread> #include<exception> #include<stdexcept> static std::exception_ptr teptr = nullptr; void f() { try { std::this_thread::sleep_for(std::chrono::seconds(1)); throw std::runtime_error("To be passed between threads"); } catch(...) { teptr = std::current_exception(); } } int main(int argc, char **argv) { std::thread mythread(f); mythread.join(); if (teptr) { try{ std::rethrow_exception(teptr); } catch(const std::exception &ex) { std::cerr << "Thread exited with exception: " << ex.what() << "\n"; } } return 0; }
因为在你的情况下你有多个工作线程,你将需要为每个线程保留一个exception_ptr。
请注意,exception_ptr是一个共享的类ptr指针,所以您将需要至less保留一个指向每个exception的exception_ptr,否则它们将被释放。
微软具体:如果您使用SEHexception(/ EHa),示例代码也将传输SEHexception,如访问冲突,这可能不是你想要的。
目前,唯一可移植的方法是为所有可能希望在线程之间传输的exceptiontypes编写catch子句,将信息存储在catch子句的某处,然后在稍后使用它来重新抛出exception。 这是Boost.Exception采取的方法。
在C ++ 0x中,你将能够用catch(...)
捕获exception,然后使用std::current_exception()
将其存储在std::exception_ptr
一个实例中。 然后,您可以使用std::rethrow_exception()
从相同或不同的线程中重新抛出它。
如果您使用的是Microsoft Visual Studio 2005或更高版本,那么just :: thread C ++ 0x线程库支持std::exception_ptr
。 (免责声明:这是我的产品)。
你的问题是,你可能会从多个线程接收多个exception,因为每个exception都可能失败,也许来自不同的原因。
我假设主线程以某种方式等待线程结束检索结果,或者定期检查其他线程的进度,并且访问共享数据是同步的。
解决scheme简单
简单的解决办法是捕获每个线程中的所有exception,将它们logging在一个共享variables(在主线程中)。
一旦所有的线程完成,决定如何处理例外。 这意味着所有其他线程继续处理,这可能不是你想要的。
解决scheme复杂
如果从另一个线程抛出exception,更复杂的解决scheme是让每个线程检查执行的关键点。
如果一个线程抛出一个exception,它在退出线程之前被捕获,exception对象被复制到主线程中的某个容器中(就像在简单的解决scheme中一样),并且一些共享的布尔variables被设置为true。
而当另一个线程testing这个布尔值时,它看到执行将被中止,并以优雅的方式中止。
当所有线程都放弃时,主线程可以根据需要处理exception。
从线程抛出的exception在父线程中不可捕获。 线程有不同的上下文和堆栈,通常父线程不需要停留在那里,等待孩子完成,这样就可以捕获他们的exception。 这个问题根本没有代码:
try { start thread(); wait_finish( thread ); } catch(...) { // will catch exceptions generated within start and wait, // but not from the thread itself }
您将需要捕获每个线程内的exception,并解释主线程中线程的退出状态,以重新引发您可能需要的任何exception。
顺便说一句,在一个线程中捕获的缺点,它是具体实现,如果堆栈展开将完成,即自动variables的析构函数甚至可能不会被调用之前终止被调用。 一些编译器这样做,但不是必需的。
你可以序列化工作线程中的exception,传回到主线程,反序列化,并再次抛出? 我期望,为了这个工作,exception将都必须从相同的类(或至less一小部分类与switch语句的东西再次)派生。 另外,我不确定它们是否可以序列化,我只是在大声思考。
如果你使用的是C ++ 11,那么std::future
可能就是你正在寻找的东西:它可以自动地捕获到达工作线程顶端的exception,并将它们传递给父线程调用了std::future::get
一点。 (在幕后,这正如@AnthonyWilliams的回答中所发生的一样,它已经被执行了。
缺点是没有标准的方法来“停止关心” std::future
; 甚至它的析构函数也会阻塞,直到任务完成。 [编辑,2017:阻塞析构函数的行为只是从std::async
返回的伪期货的一个错误,你永远不应该使用它。 正常的期货不会阻止他们的破坏者。 但是如果你使用std::future
你仍然不能“取消”任务:即使没有人正在听答案,履行诺言的任务也将继续在幕后运行。]这是一个玩具的例子,可能会澄清我的意思:
#include <atomic> #include <chrono> #include <exception> #include <future> #include <thread> #include <vector> #include <stdio.h> bool is_prime(int n) { if (n == 1010) { puts("is_prime(1010) throws an exception"); throw std::logic_error("1010"); } /* We actually want this loop to run slowly, for demonstration purposes. */ std::this_thread::sleep_for(std::chrono::milliseconds(100)); for (int i=2; i < n; ++i) { if (n % i == 0) return false; } return (n >= 2); } int worker() { static std::atomic<int> hundreds(0); const int start = 100 * hundreds++; const int end = start + 100; int sum = 0; for (int i=start; i < end; ++i) { if (is_prime(i)) { printf("%d is prime\n", i); sum += i; } } return sum; } int spawn_workers(int N) { std::vector<std::future<int>> waitables; for (int i=0; i < N; ++i) { std::future<int> f = std::async(std::launch::async, worker); waitables.emplace_back(std::move(f)); } int sum = 0; for (std::future<int> &f : waitables) { sum += f.get(); /* may throw an exception */ } return sum; /* But watch out! When f.get() throws an exception, we still need * to unwind the stack, which means destructing "waitables" and each * of its elements. The destructor of each std::future will block * as if calling this->wait(). So in fact this may not do what you * really want. */ } int main() { try { int sum = spawn_workers(100); printf("sum is %d\n", sum); } catch (std::exception &e) { /* This line will be printed after all the prime-number output. */ printf("Caught %s\n", e.what()); } }
我只是试着用std::thread
和std::exception_ptr
写一个类似于工作的例子,但std::exception_ptr
(使用libc ++)出错了,所以我还没有得到它的实际工作。 🙁
[编辑,2017:
int main() { std::exception_ptr e; std::thread t1([&e](){ try { ::operator new(-1); } catch (...) { e = std::current_exception(); } }); t1.join(); try { std::rethrow_exception(e); } catch (const std::bad_alloc&) { puts("Success!"); } }
我不知道2013年我做错了什么,但我确定这是我的错。]
事实上,没有一个好的,通用的方法来将exception从一个线程传送到另一个线程。
如果,因为它应该,所有你的exception派生std ::exception,那么你可以有一个顶级的一般exception捕获,将以某种方式发送exception到主线程,它将被抛出。 问题是你失去了例外的抛出点。 你也许可以编写依赖于编译器的代码来获取这些信息并通过它传递。
如果不是所有的exceptioninheritance了std :: exception,那么你就麻烦了,不得不在你的线程中编写大量的顶级catch …但是解决scheme依然成立。
你将需要为worker中的所有exception(包括非stdexception,比如访问冲突)做一个通用的捕获,并且从worker线程(我想你有一些types的消息到位?线程,包含一个指向exception的活动指针,并通过创buildexception副本重新抛出。 然后工人可以释放原始物体并退出。
请参阅http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html 。 也可以写一个你调用的任何函数的包装函数来join一个子线程,该子线程会自动重新引发(使用boost :: rethrow_exception)子线程发出的任何exception。
- 从脚本导入已安装的软件包引发“AttributeError:模块没有属性”或“ImportError:无法导入名称”
- 处理dynamic时引发大量的Microsoft.CSharp.RuntimeBinderExceptions
- 你如何断言在JUnit 4testing中引发了某个exception?
- 如何将traceback / sys.exc_info()值保存在variables中?
- SemaphoreFullException通过ASP.NET成员资格检查用户angular色时
- Python中是否有Java IllegalStateExceptionexception?
- Python:我怎么知道哪些exception可能会从方法调用中抛出
- 何时使用断言以及何时使用exception
- 什么是StackOverflowError?