调用 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 通常会阻塞,直到结果设置为未来。这个结果也可以是一个例外。因此从另一个线程设置一个结果将解除阻塞获取。如果异常传播到未来,也会发生同样的情况。

这是 link 到 cppreference.com 描述的:

The get method waits until the future has a valid result and (depending on which template is used) retrieves it. It effectively calls wait() in order to wait for the result.

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

可能会发生,你很幸运,等待未来,它会为最后一个排队的作业提供结果,因此所有其他作业都已完成,你不会注意到任何阻塞,但反之亦然.

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

The template function async runs the function f asynchronously (potentially in a separate thread which may be part of a thread pool) and returns a std::future that will eventually hold the result of that function call.

进一步说明:

If the async flag is set (i.e. policy & std::launch::async != 0), then async executes the callable object f on a new thread of execution (with all thread-locals initialized) as if spawned by std::thread(std::forward(f), std::forward(args)...), except that if the function f returns a value or throws an exception, it is stored in the shared state accessible through the std::future that async returns to the caller.

您能否先尝试 运行 没有任何策略的 std::async 版本,它 should/might 重用内部线程池。如果它 运行s 更快,那么问题可能是应用程序没有重新使用线程?

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

The implementation may extend the behavior of the first overload of std::async by enabling additional (implementation-defined) bits in the default launch policy. Examples of implementation-defined launch policies are the sync policy (execute immediately, within the async call) and the task policy (similar to async, but thread-locals are not cleared)

If the std::future obtained from std::async is not moved from or bound to a reference, the destructor of the std::future will block at the end of the full expression until the asynchronous operation completes, essentially making code such as the following synchronous:

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

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

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

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

The template function async runs the function f asynchronously (potentially in a separate thread which may be part of a thread pool) and returns a std::future that will eventually hold the result of that function call.

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

If the async flag is set (i.e. policy & std::launch::async != 0), then async executes the callable object f on a new thread of execution (with all thread-locals initialized) as if spawned by std::thread(std::forward<F>(f), std::forward<Args>(args)...), except that if the function f returns a value or throws an exception, it is stored in the shared state accessible through the std::future that async returns to the caller.

但是,就您的问题而言,您只是想知道与您对 get 的调用相关的执行时间。很容易证明 getstd::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;
}

输出(live example here):

[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