重新分配到尚未准备好的未来时会发生什么
What happens when reassigning to a future that is not ready yet
在代码审查期间,我遇到了一段代码,基本上可以归结为:
#include <iostream>
#include <future>
#include <thread>
int main( int, char ** )
{
std::atomic<int> x( 0 );
std::future<void> task;
for( std::size_t i = 0u; i < 5u; ++i )
{
task = std::async( std::launch::async, [&x, i](){
std::this_thread::sleep_for( std::chrono::seconds( 2u * ( 5u - i ) ) );
++x;
} );
}
task.get();
std::cout << x << std::endl;
return 0;
}
我不太确定是否
- 保证在打印结果时执行所有任务, 任务
- 是否会一个接一个地执行(即任务分配会阻塞(。
我无法通过阅读互联网上的文档来回答这个问题,所以我想我会写上面的片段来找出我们的编译器实际做了什么。
现在,我发现gcc-5所做的答案是不确定的,这让我更加好奇:人们会假设分配是阻塞或非阻塞。
如果是阻塞,程序所花费的时间基本上应该是各个任务执行所花费的时间的总和。第一个需要 10 秒,第二个需要 8 秒,第三个需要 6 秒,第四个需要 4 秒,最后 2 秒。所以总共需要 10+8+6+4+2 = 30 秒。
如果是非阻塞,则应该与最后一个任务一样长,即 2 秒。
事情是这样的:它需要18秒(使用时间./a.out或一个好的旧时钟测量(。通过对代码进行一些操作,我发现代码的行为就像赋值是交替阻塞和非阻塞一样。
但这不可能是真的,对吧? std::async
可能会回落到std::deferred
一半的时间?我的调试器说它生成两个线程,阻塞直到两个线程退出,然后生成另外两个线程,依此类推。
标准怎么说?应该怎么做?GCC-5内部会发生什么?
一般来说,通过operator=(&&)
task
赋值不必是阻塞的(见下文(,但由于您使用std::async
创建了std::future
,这些赋值成为阻塞(感谢@T.C.(:
[未来.异步]
如果实现选择启动::异步策略,
[...]
关联的线程完成与 ([intro.multithread]( 成功检测共享状态的就绪状态的第一个函数的返回同步,或与释放共享状态的最后一个函数的返回同步,以先发生者为准。
为什么你会得到 18 秒的执行时间?
在您的情况下,std::async
在分配之前启动 lambda 的"线程" — 有关如何获得 18 秒执行时间的详细说明,请参阅下文。
这是(可能(在你的代码中发生的情况(e
代表epsilon(:
-
t = 0
,首先std::async
与i = 0
调用,开始一个新线程; -
t = 0 + e
,i = 1
启动新线程的第二个std::async
调用,然后移动。此举将释放task
的当前共享状态,阻塞约 10 秒(但带有i = 1
的第二个std::async
已经在执行(; -
t = 10
,第三次std::async
调用,i = 2
启动一个新线程,然后移动。task
的当前共享状态是与i = 1
的调用,它已经准备就绪,因此没有任何阻塞; -
t = 10 + e
,第四std::async
调用,i = 3
启动一个新线程,然后移动。移动被阻止,因为之前的std::async
i = 2
尚未准备就绪,但i = 3
线程已经开始; -
t = 16
,第五次std::async
调用,i = 4
启动一个新线程,然后移动。task
(i = 3
(的当前共享状态已经准备就绪,因此非阻塞; -
t = 16 + e
,在循环之外,调用.get()
等待 *共享状态' 准备就绪; -
t = 18
,共享状态变得准备就绪,所以整个东西结束了。
std::future::operator=
的标准细节:
以下是operator=
std::future
的标准报价:
future& operator=(future&& rhs) noexcept;
影响:
- (10.1( — 释放任何共享状态 (30.6.4(。
- 。
以下是"释放任何共享状态">的含义(重点是我的(:
当异步返回对象或异步提供程序被称为释放其共享状态时,这意味着:
(5.1( — [...]
(5.2( — [...]
(5.3( — 这些操作不会阻止共享状态变为就绪状态,但如果满足以下所有条件,则可能会阻止:共享状态是通过调用 std::async 创建的,共享状态尚未 就绪,这是对共享状态的最后一次引用。
你的案子属于我强调的(我认为(。您使用 std::async
创建了共享状态,它处于睡眠状态(因此尚未准备好(,并且您只有一个对它的引用,因此这可能是阻塞。
保证在打印结果时执行所有任务,
只有最后分配的任务才能保证已执行。至少,我找不到任何可以保证其余的规则。
任务是否会一个接一个地执行(即任务分配会阻塞(。
任务分配通常是非阻塞的,但在这种情况下可能会阻塞 - 无法保证。
[futures.unique_future]
future& operator=(future&& rhs( noexcept;
影响:
释放任何共享状态([futures.state](。
[期货状态]
当异步返回对象或异步提供程序被称为释放其共享状态时,这意味着:
如果返回对象或提供程序保存对其共享状态的最后一个引用,则共享状态将被销毁;和
返回对象或提供程序放弃对其共享状态的引用;以及
这些操作不会阻止共享状态变为就绪状态,但如果满足以下所有条件,则可能会阻止:共享状态是通过调用 std::async 创建的,共享状态尚未就绪,这是对共享状态的最后一次引用。
潜在阻塞的所有条件都适用于尚未执行std::async
创建的任务。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 警告处理为错误这里有什么问题
- 什么时候调用组成单元对象的析构函数
- #定义c-预处理器常量..我做错了什么
- 努力将整数转换为链表。不知道我在这里做错了什么
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 什么时候在C++中返回常量引用是个好主意
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- C++避免重复声明的语法是什么
- 在Linux中哪里可以找到互斥、未来等的源代码
- c++库的公共头文件中应该包含什么
- 问题:什么是QAbstractItemView::NoEditTriggers的反面
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- ifstream什么都没读
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- 一个计算机专业二年级的学生能做些什么,这在未来可能会被认为是有价值的
- 重新分配到尚未准备好的未来时会发生什么