繁体   English   中英

线程是否真的在 std::future::get() 被调用后启动?

[英]Does a thread really start after std::future::get() is called?

我创建了一个 for 循环来定义std::futurestd::vector来执行我的函数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以指示结果来自哪个线程。

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

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

通常,在未来模式中get通常会阻塞,直到结果设置为未来。 这个结果也可能是一个例外。 因此,设置来自其他线程的结果将解除对 get 的阻塞。 如果异常传播到未来,也会发生同样的情况。

这是cppreference.com的链接,它描述了这一点:

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

线程不能保证按照您创建它们的顺序运行,甚至不能保证按照应用程序分配的顺序运行。 为它做。

可能会发生,您很幸运并等待未来,它承担最后一个排队作业的结果,因此所有其他工作都完成了,您没有注意到任何阻塞,但反之亦然。

std::async :不保证任何异步执行。 以下是参考资料

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

进一步说明:

如果设置了 async 标志(即 policy & std::launch::async != 0),则 async 在新的执行线程(初始化所有线程局部变量)上执行可调用对象 f,就像由 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),并通过注销线程 ID 以及这些线程是否被重新使用来记录那里发生的事情以及std::async创建了多少个不同的线程根本。

因为您使用的是std::launch::async ,所以由std::async决定如何安排您的请求。 根据cppreference.com

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

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

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

对于你的问题的目的,但是,你只是想,当它在关系的执行您的来电就知道get 当使用std::launch::async时,很容易证明get与异步任务的执行无关:

#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;
}

输出(此处为现场示例):

[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()时,您可以预期这些任务中的部分或全部可能仍在执行。 在这种情况下,您的线程将被挂起,直到与该未来关联的承诺得到满足。 此外,由于异步任务可能会被合并,因此有些任务可能会在其他任务完成后才开始评估。

如果您改为使用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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM