由于似乎对异常传播有正当的兴趣,并且这与问题略有相关,因此我的建议是: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::async、std::future 以及std::packaged_task、std::promise 和std::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