重新分配到尚未准备好的未来时会发生什么

时间:2016-07-01 08:58:56

标签: c++ c++11 c++14 future

在代码审查期间,我遇到了一段代码,基本上归结为:

#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里面发生了什么?

2 个答案:

答案 0 :(得分:12)

一般情况下,task通过operator=(&&)的分配不一定要阻止(见下文),但由于您使用std::future创建了std::async,因此这些分配变得阻塞(感谢@TC):

[future.async]

  

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

     
      
  • [...]

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

  •   

为什么你得到18秒的执行时间?

在你的情况下会发生std::async启动&#34;线程&#34;对于你的lambda 之前作业 - 请参阅下面有关如何获得18秒执行时间的详细说明。

这是(可能)在您的代码中发生的事情(e代表epsilon):

  • t = 0,首先std::async致电i = 0,开始新线程;
  • t = 0 + e,第二个std::async调用i = 1开始一个新主题,然后移动。移动将释放task的当前共享状态,阻塞大约10秒(但第std::asynci = 1已经执行);
  • 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开始一个新主题,然后移动。 taski = 3)的当前共享状态已准备就绪,因此无阻塞;
  • t = 16 + e,在循环之外,调用.get()等待*共享状态`准备就绪;
  • t = 18共享状态已准备好,所以整个内容结束。

std::future::operator=的标准详情:

以下是operator=std::future的标准报价:

future& operator=(future&& rhs) noexcept;
     

效果:

     
      
  • (10.1) - 释放任何共享状态(30.6.4)。
  •   
  • ...
  •   

以下是&#34;发布任何共享状态&#34; 意味着(强调是我的):

  

当说异步返回对象或异步提供程序释放其共享状态时,它意味着:

     

(5.1) - [...]

     

(5.2) - [...]

     

(5.3) - 这些操作不会阻止共享状态准备就绪,除非可能阻止如果满足以下所有条件:共享状态是由a创建的调用std :: async,共享状态还没有   准备就绪,这是对共享状态的最后一次引用

你的情况属于我强调的(我认为)。您使用std::async创建了共享状态,它处于休眠状态(因此尚未就绪),并且您只有一个引用状态,因此可能会阻止。

答案 1 :(得分:3)

  

保证在打印出结果时执行所有任务,

只保证执行最后分配的任务。至少,我找不到任何可以保证其余的规则。

  

是否将一个接一个地执行任务(即任务分配将被阻止)。

任务分配通常是非阻塞的,但在这种情况下可能会阻止 - 无法保证。

[futures.unique_future]

  

未来&安培; operator =(future&amp;&amp; rhs)noexcept;

     
      
  1. 效果:

         

    释放任何共享状态([futures.state])。

  2.   

[futures.state]

  
      
  1. 当说异步返回对象或异步提供程序释放其共享状态时,它意味着:

         
        
    • 如果返回对象或提供者持有对其共享状态的最后一个引用,则共享状态将被销毁;以及

    •   
    • 返回对象或提供者放弃对其共享状态的引用;以及

    •   
    • 这些操作不会阻止共享状态变为就绪,除非它可以阻止如果满足以下所有条件:共享状态是通过调用std创建的: :async,共享状态尚未就绪,这是共享状态的最后一个引用。

    •   
  2.   

潜在阻止的所有条件都适用于std::async尚未执行的任务。