C++ 如何在线程之间传播异常?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/233127/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I propagate exceptions between threads?
提问by pauldoo
We have a function which a single thread calls into (we name this the main thread). Within the body of the function we spawn multiple worker threads to do CPU intensive work, wait for all threads to finish, then return the result on the main thread.
我们有一个单线程调用的函数(我们将其命名为主线程)。在函数体内,我们生成多个工作线程来执行 CPU 密集型工作,等待所有线程完成,然后在主线程上返回结果。
The result is that the caller can use the function naively, and internally it'll make use of multiple cores.
结果是调用者可以天真地使用该函数,并且在内部它将使用多个内核。
All good so far..
到目前为止一切都很好..
The problem we have is dealing with exceptions. We don't want exceptions on the worker threads to crash the application. We want the caller to the function to be able to catch them on the main thread. We must catch exceptions on the worker threads and propagate them across to the main thread to have them continue unwinding from there.
我们遇到的问题是处理异常。我们不希望工作线程上的异常导致应用程序崩溃。我们希望函数的调用者能够在主线程上捕获它们。我们必须在工作线程上捕获异常并将它们传播到主线程,让它们继续从那里展开。
How can we do this?
我们应该怎么做?
The best I can think of is:
我能想到的最好的是:
- Catch a whole variety of exceptions on our worker threads (std::exception and a few of our own ones).
- Record the type and message of the exception.
- Have a corresponding switch statement on the main thread which rethrows exceptions of whatever type was recorded on the worker thread.
- 在我们的工作线程上捕获各种各样的异常(std::exception 和一些我们自己的异常)。
- 记录异常的类型和消息。
- 在主线程上有一个相应的 switch 语句,它会重新抛出工作线程上记录的任何类型的异常。
This has the obvious disadvantage of only supporting a limited set of exception types, and would need modification whenever new exception types were added.
这具有仅支持有限的异常类型集的明显缺点,并且每当添加新的异常类型时都需要进行修改。
采纳答案by Gerardo Hernandez
C++11 introduced the exception_ptr
type that allows to transport exceptions between threads:
C++11 引入了exception_ptr
允许在线程之间传输异常的类型:
#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;
}
Because in your case you have multiple worker threads, you will need to keep one exception_ptr
for each of them.
因为在您的情况下,您有多个工作线程,您需要exception_ptr
为每个工作线程保留一个。
Note that exception_ptr
is a shared ptr-like pointer, so you will need to keep at least one exception_ptr
pointing to each exception or they will be released.
请注意,这exception_ptr
是一个共享的类似 ptr 的指针,因此您需要至少保留一个exception_ptr
指向每个异常的指针,否则它们将被释放。
Microsoft specific: if you use SEH Exceptions (/EHa
), the example code will also transport SEH exceptions like access violations, which may not be what you want.
Microsoft 特定:如果您使用 SEH Exceptions ( /EHa
),示例代码还将传输 SEH 异常,例如访问冲突,这可能不是您想要的。
回答by Anthony Williams
Currently, the only portableway is to write catch clauses for all the types of exceptions that you might like to transfer between threads, store the information somewhere from that catch clause and then use it later to rethrow an exception. This is the approach taken by Boost.Exception.
目前,唯一可移植的方法是为您可能希望在线程之间传输的所有类型的异常编写 catch 子句,将来自该 catch 子句的信息存储在某处,然后稍后使用它来重新抛出异常。这是Boost.Exception采取的方法。
In C++0x, you will be able to catch an exception with catch(...)
and then store it in an instance of std::exception_ptr
using std::current_exception()
. You can then rethrow it later from the same or a different thread with std::rethrow_exception()
.
在 C++0x 中,您将能够捕获异常,catch(...)
然后将其存储在std::exception_ptr
using的实例中std::current_exception()
。然后您可以稍后从相同或不同的线程重新抛出它std::rethrow_exception()
。
If you are using Microsoft Visual Studio 2005 or later, then the just::thread C++0x thread librarysupports std::exception_ptr
. (Disclaimer: this is my product).
如果您使用的是 Microsoft Visual Studio 2005 或更高版本,则just::thread C++0x 线程库支持std::exception_ptr
. (免责声明:这是我的产品)。
回答by Quuxplusone
If you're using C++11, then std::future
might do exactly what you're looking for: it can automagically trap exceptions that make it to the top of the worker thread, and pass them through to the parent thread at the point that std::future::get
is called. (Behind the scenes, this happens exactly as in @AnthonyWilliams' answer; it's just been implemented for you already.)
如果您使用C ++ 11,那么std::future
可能你正在寻找什么:它可以自动地捕获异常,它作出的工作线程的顶部,并通过父线程的一点通过他们std::future::get
是叫。(在幕后,这与@AnthonyWilliams 的回答完全一样;它已经为您实现了。)
The down side is that there's no standard way to "stop caring about" a std::future
; even its destructor will simply block until the task is done. [EDIT, 2017: The blocking-destructor behavior is a misfeature onlyof the pseudo-futures returned from std::async
, which you should never use anyway. Normal futures don't block in their destructor. But you still can't "cancel" tasks if you're using std::future
: the promise-fulfilling task(s) will continue running behind the scenes even if nobody is listening for the answer anymore.]Here's a toy example that might clarify what I mean:
不利的一面是没有标准的方法来“停止关心” a 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());
}
}
I just tried to write a work-alike example using std::thread
and std::exception_ptr
, but something's going wrong with std::exception_ptr
(using libc++) so I haven't gotten it to actually work yet. :(
我只是尝试使用std::thread
and编写一个类似工作的示例std::exception_ptr
,但是std::exception_ptr
(使用 libc++)出了点问题,所以我还没有让它实际工作。:(
[EDIT, 2017:
[编辑,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!");
}
}
I have no idea what I was doing wrong in 2013, but I'm sure it was my fault.]
我不知道我在 2013 年做错了什么,但我确定这是我的错。]
回答by paercebal
You problem is that you could receive multiple exceptions, from multiple threads, as each could fail, perhaps from different reasons.
您的问题是您可能会收到来自多个线程的多个异常,因为每个都可能失败,可能是出于不同的原因。
I am assuming the main thread is somehow waiting for the threads to end to retrieve the results, or checking regularly the other threads' progress, and that access to shared data is synchronized.
我假设主线程以某种方式等待线程结束以检索结果,或者定期检查其他线程的进度,并且对共享数据的访问是同步的。
Simple solution
简单的解决方案
The simple solution would be to catch all exceptions in each thread, record them in a shared variable (in the main thread).
简单的解决方案是捕获每个线程中的所有异常,将它们记录在共享变量中(在主线程中)。
Once all threads finished, decide what to do with the exceptions. This means that all other threads continued their processing, which perhaps, is not what you want.
所有线程完成后,决定如何处理异常。这意味着所有其他线程继续处理,这可能不是您想要的。
Complex solution
复杂的解决方案
The more complex solution is have each of your threads check at strategic points of their execution, if an exception was thrown from another thread.
更复杂的解决方案是让您的每个线程在其执行的关键点进行检查,如果异常是从另一个线程抛出的。
If a thread throws an exception, it is caught before exiting the thread, the exception object is copied into some container in the main thread (as in the simple solution), and some shared boolean variable is set to true.
如果一个线程抛出一个异常,它会在退出线程之前被捕获,异常对象被复制到主线程中的某个容器中(如在简单的解决方案中),并且一些共享的布尔变量被设置为 true。
And when another thread tests this boolean, it sees the execution is to be aborted, and aborts in a graceful way.
当另一个线程测试这个布尔值时,它看到执行将被中止,并以一种优雅的方式中止。
When all thread did abort, the main thread can handle the exception as needed.
当所有线程确实中止时,主线程可以根据需要处理异常。
回答by n-alexander
An exception thrown from a thread will not be catchable in the parent thread. Threads have different contexts and stacks, and generally the parent thread is not required to stay there and wait for the children to finish, so that it could catch their exceptions. There is simply no place in code for that catch:
从线程抛出的异常将无法在父线程中捕获。线程有不同的上下文和堆栈,通常不需要父线程留在那里等待子线程完成,以便它可以捕获它们的异常。在代码中根本没有地方用于该捕获:
try
{
start thread();
wait_finish( thread );
}
catch(...)
{
// will catch exceptions generated within start and wait,
// but not from the thread itself
}
You will need to catch exceptions inside each thread and interpret exit status from threads in the main thread to re-throw any exceptions you might need.
您需要在每个线程内捕获异常并解释主线程中线程的退出状态以重新抛出您可能需要的任何异常。
BTW, in the absents of a catch in a thread it is implementation specific if stack unwinding will be done at all, i.e. your automatic variables' destructors may not even be called before terminate is called. Some compilers do that, but it's not required.
顺便说一句,在线程中没有捕获的情况下,如果堆栈展开将完全完成,则是实现特定的,即在调用终止之前甚至可能不会调用自动变量的析构函数。一些编译器会这样做,但这不是必需的。
回答by tvanfosson
Could you serialize the exception in the worker thread, transmit that back to the main thread, deserialize, and throw it again? I expect that for this to work the exceptions would all have to derive from the same class (or at least a small set of classes with the switch statement thing again). Also, I'm not sure that they would be serializable, I'm just thinking out loud.
您能否在工作线程中序列化异常,将其传输回主线程,反序列化并再次抛出它?我希望要使其正常工作,所有异常都必须从同一个类派生(或者至少是一小组带有 switch 语句的类)。另外,我不确定它们是否可以序列化,我只是在大声思考。
回答by PierreBdR
There is, indeed, no good and generic way to transmit exceptions from one thread to the next.
实际上,没有一种好的通用方法可以将异常从一个线程传输到下一个线程。
If, as it should, all your exceptions derive from std::exception, then you can have a top-level general exception catch that will somehow send the exception to the main thread where it will be thrown again. The problem being you loose the throwing point of the exception. You can probably write compiler-dependent code to get this information and transmit it though.
如果,正如它应该的那样,您的所有异常都源自 std::exception,那么您可以拥有一个顶级通用异常捕获,它会以某种方式将异常发送到主线程,在那里它将再次抛出。问题是你失去了异常的抛出点。您可能可以编写依赖于编译器的代码来获取此信息并传输它。
If not all your exception inherit std::exception, then you are in trouble and have to write a lot of top-level catch in your thread ... but the solution still hold.
如果不是所有的异常都继承了 std::exception,那么你就有麻烦了,必须在你的线程中编写很多顶级捕获......但解决方案仍然有效。
回答by anon6439
You will need to do a generic catch for all exceptions in the worker (including non-std exceptions, like access violations), and send a message from the worker thread (i suppose you have some kind of messaging in place?) to the controlling thread, containing a live pointer to the exception, and rethrow there by creating a copy of the exception. Then the worker can free the original object and exit.
您将需要对工作线程中的所有异常(包括非标准异常,如访问冲突)进行通用捕获,并从工作线程(我想您有某种消息传递?)发送消息到控制线程,包含指向异常的活动指针,并通过创建异常的副本重新抛出该异常。然后工作人员可以释放原始对象并退出。
回答by Emil
See http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html. It is also possible to write a wrapper function of whatever function you call to join a child thread, which automatically re-throws (using boost::rethrow_exception) any exception emitted by a child thread.
请参阅http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html。也可以为您调用的任何函数编写一个包装函数来加入子线程,它会自动重新抛出(使用 boost::rethrow_exception)子线程发出的任何异常。