在调用std :: future :: get()之后线程是否真的开始了?

时间:2017-05-09 07:01:10

标签: c++ multithreading c++11 concurrent.futures

我创建了一个for循环,用于定义std::vector std::future来执行我的函数vector<int> identify,另一个循环通过调用std::future::get()获取结果,如下所示:

for (int i = 0; i < NUM_THREADS; ++i) {
    VecString::const_iterator first = dirList.begin() + i*share;
    VecString::const_iterator last = i == NUM_THREADS - 1 ? dirList.end() : dirList.begin() + (i + 1)*share;
    VecString job(first, last);
    futures[i] = async( launch::async, [=]() -> VecInt {
        return identify(i, job, make_tuple( bIDList, wIDList, pIDList, descriptor), testingDir, binaryMode, logFile, logFile2 );
    } );
}

int correct = 0;
int numImages = 0;
for( int i = 0; i != NUM_THREADS; ++i ) {
    VecInt ret = futures[i].get();
    correct += ret[0];
    numImages += ret[1];
}

工作是处理一些图像,我在每个线程之间大致平均分工。我还在函数中嵌入std::cout以指示结果来自哪个线程。

我希望在第一个线程完成其工作之后,其他人也应该完成它们的工作,并且循环将打印出结果。但是,在第一个线程完成后,其他线程仍然有效。我认为它们确实有效,而不仅仅是打印结果,因为当函数处理大图像时会有一些延迟。这让我想知道线程何时真正开始。

我从文档中知道每个线程在初始化后立即启动但是如何解释我的观察?非常感谢,非常感谢任何帮助

2 个答案:

答案 0 :(得分:3)

通常,在将来模式get通常会阻塞,直到结果设置为将来。这个结果也可能是一个例外。因此,从另一个线程设置结果将取消阻止get。如果将异常传播到将来,也会出现相同的情况。

以下是cppreference.com的链接,其中描述了这一点:

  

get方法一直等到将来有一个有效的结果,并且(取决于使用的模板)检索它。它有效地调用wait()以等待结果。

不保证线程按照您创建它们的顺序运行,甚至不能在分配给smth的订单应用程序中运行。它可以做到。

可能会发生这种情况,你很幸运,等待将来承担最后一个排队工作的结果,因此所有其他人都完成了,你没有注意到任何阻塞,但反之亦然。< / p>

std :: async :不保证任何异步执行。这是reference states

  

模板函数async异步运行函数f(可能在一个单独的线程中,它可能是线程池的一部分)并返回一个最终将保存该函数结果的std :: future调用

进一步说明:

  

如果设置了async标志(即policy&amp; std :: launch :: async!= 0),则async在新执行线程上执行可调用对象f(使用所有线程) -locals初始化)好像由std :: thread(std :: forward(f),std :: forward(args)...)生成,除非函数f返回一个值或抛出一个异常,它被存储在通过std :: future访问的共享状态中,异步返回给调用者。

您是否可以首先尝试在没有任何政策的情况下运行std::async版本,应该/可能重用内部线程池。如果它运行得更快,问题可能是应用程序不重用线程?

最后,对async的引用有一个注释,当执行可以同步时说明:

  

实现可以通过在默认启动策略中启用其他(实现定义的)位来扩展std :: async的第一个重载的行为。   实现定义的启动策略的示例是同步策略(在异步调用中立即执行)和任务策略(类似于异步,但不清除线程本地)

     

如果从std :: async获取的std :: future未从引用移动或绑定到引用,则std :: future的析构函数将在完整表达式结束时阻塞,直到异步操作完成,基本上制作代码,如以下同步

std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes

调试多线程应用程序有点困难。我建议只创建一个额外的线程,让所有的工作/期货通过它运行,看看执行中是否存在一些误解。此时主线程不会因为等待结果而打扰。

您还可以使用一些线程安全的日志库(例如Boost Log)并记录那里发生的事情以及std::async通过注销thread-id创建了多少个不同的线程,如果这些线程被重复使用。

答案 1 :(得分:1)

由于您使用的是std::launch::async,因此最多可以std::async来确定如何安排您的请求。根据{{​​3}}:

  

模板函数async异步运行函数f(可能在一个单独的线程中,它可能是线程池的一部分)并返回一个std::future,最终将保存该函数调用的结果。

但它确实保证它们会被线程化,你可以推断你的lambda的评估将安排在下一个可能的机会发生:

  

如果设置了 async 标志(即policy & std::launch::async != 0),则async在新的执行线程上执行可调用对象f(所有线程局部件已初始化),就像产生的一样std::thread(std::forward<F>(f), std::forward<Args>(args)...),除非函数f返回值或抛出异常,它将存储在可通过std::future访问的共享状态,异步返回给调用者。

但是,就您的问题而言,您只想了解与get的通话相关的执行时间。使用get启动时,std::launch::async与执行异步任务无关,这很容易证明:

#include <iostream>
#include <future>
#include <thread>
#include <vector>
#include <chrono>

using namespace std;

int main() {
    auto start = chrono::steady_clock::now();
    auto timestamp = [start]( ostream & s )->ostream& {
        auto now = chrono::steady_clock::now();
        auto elapsed = chrono::duration_cast<chrono::microseconds>(now - start);
        return s << "[" << elapsed.count() << "us] ";
    };

    vector<future<int>> futures;
    for( int i = 0; i < 5; i++ )
    {
        futures.emplace_back( async(launch::async,
            [=](){
                timestamp(cout) << "Launch " << i << endl;
                return i;
            } ) );
    }

    this_thread::sleep_for( chrono::milliseconds(100) );

    for( auto & f : futures ) timestamp(cout) << "Get " << f.get() << endl;

    return 0;
}

输出(cppreference.com):

[42us] Launch 4
[85us] Launch 3
[95us] Launch 2
[103us] Launch 1
[109us] Launch 0
[100134us] Get 0
[100158us] Get 1
[100162us] Get 2
[100165us] Get 3
[100168us] Get 4

这些操作很简单,但是如果您有长时间运行的任务,那么当您致电std::future<T>::get()时,您可能会发现部分或全部任务可能仍在执行。在这种情况下,您的线程将被暂停,直到满足与 future 相关联的 promise 。此外,由于可能会汇集异步任务,因此有些人可能会在其他任务完成之后才开始评估。

如果您使用std::launch::deferred,那么您将在调用线程上获得延迟评估,因此输出将类似于:

[100175us] Launch 0
[100323us] Get 0
[100340us] Launch 1
[100352us] Get 1
[100364us] Launch 2
[100375us] Get 2
[100386us] Launch 3
[100397us] Get 3
[100408us] Launch 4
[100419us] Get 4
[100430us] Launch 5