假设我们有以下代码:
std::promise<int> promise;
auto future = promise.get_future();
const auto task = [](auto promise) {
try {
promise.set_value_at_thread_exit(int_generator_that_can_throw());
} catch (...) {
promise.set_exception_at_thread_exit(std::current_exception());
}
};
std::thread thread(task, std::move(promise));
// use future
thread.join();
字符串
我想知道这个代码是否正确和安全,如果不是,为什么。
用GCC编译时,它似乎工作得很好,但会崩溃(没有消息打印)当使用MSVC(2017)编译时。我的猜测是崩溃发生,因为task
内部的promise
局部变量超出范围并过早销毁。如果我删除_at_thread_exit
后缀,这段代码按预期工作(或看起来工作)。当promise被捕获时,它也能正确工作:
const auto task = [p = std::move(promise)]() mutable {
/*...*/
};
3条答案
按热度按时间p1iqtdky1#
为什么你的代码会产生问题?让我们从answer到“当
_at_thread_exit
写入std::future
和std::promise
的共享状态时?”开始。它发生在所有线程局部变量被销毁之后。你的lambda在线程内被调用,在它的作用域离开之后,promise已经被销毁了。但是当线程调用你的lambda时有一些线程局部变量会发生什么?好吧,写操作将在销毁std::promise
对象后进行。实际上,其余的在标准中是未定义的。似乎可以在销毁std::promise
后将数据传递到共享状态,但信息并不存在。最简单的解决方案当然是这样的:
字符串
f8rj6qna2#
我最近奋进理解future-promise的机制,也遇到了同样的问题。我读了源代码和C++标准,终于弄明白了是怎么回事。问题不在于“promise被析构,所以垃圾内存被访问”,而是它的不一致性。
TL;DR
如果
xx_at_thread_exit
被调用,你不应该让std::promise
在线程退出之前析构(即传递函数的结尾),因为它会尝试设置值和异常。看起来MS-STL做了标准中规定的事情,而libc++/libstdc++没有。Future-Promise模型
在C++中,promise表示“承诺给予某些结果”的工作者,而future表示“想要未来结果”的收集器。此外,这样的连接是nonce通道,这意味着每个端只能设置/获取结果一次,除非你把一个
std::future
变成一个std::shared_future
,使它能够得到多次。共享状态有三种情况:
std::promise
可以调用set_value
,set_exception
,set_value_at_thread_exit
和set_exception_at_thread_exit
一次。std::promise
调用xx_at_thread_exit
,所以当传递给线程的函子结束时(以及在所有局部变量被析构之后),它将准备就绪。set_value
或set_exception
,或xx_at_thread_exit
当函子完全结束时。特别地,当promise在ready之前被析构时,共享状态将在dtor中被 * 放弃 *,这意味着将设置异常
std::future_error
(破碎的promise)。共享状态就像
std::future
和std::promise
都持有的std::shared_ptr
;无论谁销毁,都会使引用计数为-1,当引用计数为0时,共享状态被删除。终身关注
所以现在我们知道了,调用
xx_at_thread_exit
后,结果被存储了。使ready
完全处于共享状态(通常的实现会存储一个条件变量,并在其上注册这样的事件),这不会受到std::promise
的销毁的干扰。所以我说问题不在于“promise被销毁,所以垃圾内存被访问”。那为什么MS-STL会终止程序呢?那是因为C标准规定 abandon 不检查是否设置了结果,而是检查是否准备好了。但是
set_value_at_thread_exit
还没有让它准备好,所以会设置异常std::future_error
(broken_promise)。然而,一个共享状态应该有一个值或一个异常!MS-STL基本上只是调用
set_exception
,所以存储的值使它抛出std::future_error
(promise_already_satisfied)。但是它在noexcept
dtor中!所以std::terminate
被调用,因此程序被中止。对于libc/libstdc++,
std::promise
一旦设置,就会切断与存储结果的连接,这样dtor中就不会设置异常,程序正常运行,这稍微违反了标准,因为它应该同时有值和异常;但这将违反未来承诺模型。因此,除非修订标准,MS-STL的实现更合理,既能满足标准,又能满足抽象模型。6vl6ewon3#
字符串