std::list<std::future>析构函数不会阻塞

11

我有一个多线程应用程序,其中一个循环等待用户输入作为主线程。在正确的输入下,它应该停止循环并等待所有其他线程结束。

为此,我创建了一个 std::list,其中放置了为线程创建创建的std::future对象。

std::list<std::future<int>> threads;
threads.emplace_front(std::async(std::launch::async, ...));

我曾经认为,让list超出范围应该会阻塞,直到所有线程返回其主函数,因为list的析构函数将销毁所有std::future元素,而这些元素的析构函数将等待线程结束。

编辑:由于它很相关,我在这里添加: 这是在安装了Visual Studio 2013 Professional的Win7上进行的测试。 /编辑

当我尝试时,它没有阻塞,我不得不添加

for (auto it = threads.begin(); it != threads.end(); ++it) {
    it->get();
}

为了正确地阻塞,函数需要执行到末尾。

我是否理解有误,或者我需要以不同的方式创建线程,以实现我想要的功能?


4
看起来像是个bug。什么是编译器? - ixSci
嗯,Scott Meyers在他的博客文章中提出了一个好观点,即std::async的规范要求它阻塞。有趣... - T.C.
1
https://connect.microsoft.com/VisualStudio/feedback/details/810623 - T.C.
好的,在Linux上使用clang它按预期工作,它按我预期的方式工作,所以我猜“可能”是这里重要的部分。 - Ongy
T.C. 我认为链接是正确答案,谢谢。 - Ongy
显示剩余3条评论
2个回答

13

这是一个已修复的MSVC错误,但修复程序将不会在MS发布新版本的Visual C++之前发布,可能要到2015年的某个时候。 (它还可以在新版本的CTP中使用,但对于任何生产代码来说,这是一个非常糟糕的想法...)

正如Scott Meyers在他的博客文章中所解释的,使用launch::async策略调用std::async返回的std::future的析构函数需要阻塞,直到生成的线程完成执行(§30.6.8 [futures.async] / p5):

如果实现选择launch::async策略,

  • [...]
  • 相关联的线程完成与成功检测共享状态的就绪状态的第一个函数的返回或释放共享状态的最后一个函数的返回同步(1.10),以先发生者为准。

在这种情况下,future的析构函数是“释放共享状态的最后一个函数”,因此线程完成必须与该函数的返回同步(即先于)发生。


+1 微软曾经在 VS2010 的正则表达式错误中做了类似的事情,导致序列计数错误地增加了 +1。结果是,如果要使用它们的正则表达式特定功能,您必须“破坏”您的表达式,以便它“与他们的实现一起工作”,但实际上这是错误的表达式,因此在其他任何地方都不会起作用。可以想象,微软决定在 SP1 中修复这个问题,尽管他们早就知道这个问题,这引起了人们的愤怒。至少在这里,我们可以将您的未来包装成一个可移动的对象,在销毁时调用.wait(),因此您有一个可行的替代方案。 - WhozCraig
谢谢链接!因为这个 bug 刚刚崩溃了。 - Mikhail

0

我查看了std::future的文档,并找到了std::future析构函数的说明:

释放任何共享状态。这意味着

  • 如果返回对象或提供程序持有其共享状态的最后一个引用,则销毁共享状态;以及
  • 返回对象或提供程序放弃对其共享状态的引用;以及
  • 这些操作不会阻塞共享状态变为就绪,除非满足以下所有条件:共享状态是通过调用std::async创建的,共享状态尚未准备好,并且这是对共享状态的最后一个引用。

请注意最后一点。在我看来,您必须在作用域的末尾调用get


如果通过async调用获取了该future,那么future的析构函数应该阻塞。无需在此处使用get - ixSci
@ixSci 如果不使用std::async,这种情况是否也适用? - Franz
不,这个特殊的是std::async调用。虽然很令人困惑。请查看@T.C.在原问题的评论中发布的链接。 - ixSci

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接