对 std::async 使用 std::launch::async 参数启动的线程感到困惑

Confusion about threads launched by std::async with std::launch::async parameter

我对 std::async 函数有点困惑。

规范说:

asynchronous operation being executed "as if in a new thread of execution" (C++11 §30.6.8/11).

现在,那是什么意思?

在我的理解中,代码

std::future<double> fut = std::async(std::launch::async, pow2, num);

应该在新线程上启动函数 pow2 并将变量 num 按值传递给线程,然后在将来某个时候,当函数完成时,将结果放入 fut(只要函数 pow2 有像 double pow2(double); 这样的签名)。但是规格说明 “好像”,这让我觉得整个事情有点模糊。

问题是:

在这种情况下是否总是启动一个新线程?我希望如此。我的意思是,对我来说,参数 std::launch::async 以某种方式有意义,我明确表示我确实想创建一个新线程。

和代码

std::future<double> fut = std::async(std::launch::deferred, pow2, num);

应该使 惰性求值 成为可能,方法是将 pow2 函数调用延迟到我写 var = fut.get(); 之类的东西的地步。在这种情况下,参数 std::launch::deferred 应该意味着我明确声明,我不想要一个新线程,我只是想确保在需要它的 return 值时调用该函数.

我的假设是否正确?如果不正确,请解释。

此外,我知道默认情况下函数调用如下:

std::future<double> fut = std::async(std::launch::deferred | std::launch::async, pow2, num);

在这种情况下,我被告知是否启动新线程取决于实现。同样,那是什么意思?

std::async<future>header的一部分)函数模板用于启动一个(可能)异步任务。它 return 是一个 std::future object,它最终将保存 std::async 的参数函数的 return 值。

当需要该值时,我们在 std::future 实例上调用 get();这会阻塞线程,直到未来准备就绪,然后 returns 值。 std::launch::asyncstd::launch::deferred 可以指定为 std::async 的第一个参数,以指定任务如何 运行.

  1. std::launch::async 表示函数调用必须在它自己的(新)线程上 运行。 (考虑用户@T.C.的评论)。
  2. std::launch::deferred 表示函数调用将被推迟到将来调用 wait()get() 时。在这种情况发生之前,未来的所有权可以转移到另一个线程。
  3. std::launch::async | std::launch::deferred表示执行可选择。这是默认选项(当您没有自己指定时)。它可以同步决定运行。

在这种情况下是否总是启动一个新线程?

1.开始,我们可以说总是启动一个新线程。

我在 std::launch::deferred] 上的假设是否正确?

根据2.,可以说你的假设是正确的。

这是什么意思? [关于是否启动新线程取决于实现]

来自3.,因为std::launch::async | std::launch::deferred是默认选项,也就是说模板函数的实现std::async将决定是否创建是否有新线程。这是因为某些实现可能正在检查过度调度。

警告

以下部分与您的问题无关,但我认为牢记这一点很重要。

C++ 标准规定,如果 std::future 持有对异步函数调用对应的共享状态的最后引用,则 std::future 的析构函数必须阻塞,直到异步 运行ning 函数完成。由 std::async 编辑的 std::future return 的实例将因此阻塞其析构函数。

void operation()
{
    auto func = [] { std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); };
    std::async( std::launch::async, func );
    std::async( std::launch::async, func );
    std::future<void> f{ std::async( std::launch::async, func ) };
}

此误导性代码会让您认为 std::async 调用是异步的,但实际上它们是同步的。由 std::async 编辑的 return 实例是临时的并且会阻塞,因为它们的析构函数在 std::async return 时被调用,因为它们没有分配给变量.

std::async 的第一次调用将阻塞 2 秒,随后对 std::async 的第二次调用将阻塞另外 2 秒。我们可能认为最后一次调用 std::async 不会阻塞,因为我们将它的 returned std::future 实例存储在一个变量中,但因为那是一个局部变量,最后会被销毁在范围之外,当局部变量 f 被销毁时,它实际上会在函数范围的末尾再阻塞 2 秒。

换句话说,调用 operation() 函数将阻塞同步调用它的任何线程大约 6 秒。未来版本的 C++ 标准中可能不存在此类要求。

我用来整理这些笔记的信息来源:

C++ 并发实战:实用多线程,Anthony Williams

Scott Meyers 的博客 post:http://scottmeyers.blogspot.ca/2013/03/stdfutures-from-stdasync-arent-special.html

我也对此感到困惑,运行 对 Windows 的快速测试表明异步未来将在 OS 线程池线程上 运行。一个简单的应用程序可以证明这一点,在 Visual Studio 中中断还将显示名为 "TppWorkerThread".

的执行线程
#include <future>
#include <thread>
#include <iostream>

using namespace std;

int main()
{
    cout << "main thread id " << this_thread::get_id() << endl;

    future<int> f1 = async(launch::async, [](){
        cout << "future run on thread " << this_thread::get_id() << endl;
        return 1;
    });

    f1.get(); 

    future<int> f2 = async(launch::async, [](){
        cout << "future run on thread " << this_thread::get_id() << endl;
        return 1;
    });

    f2.get();

    future<int> f3 = async(launch::async, [](){
        cout << "future run on thread " << this_thread::get_id() << endl;
        return 1;
    });

    f3.get();

    cin.ignore();

    return 0;
}

将导致类似于以下的输出:

main thread id 4164
future run on thread 4188
future run on thread 4188
future run on thread 4188

事实并非如此。 添加 thread_local 个存储值,你会看到,实际上 std::async run f1 f2 f3 个任务在不同的线程中,但具有相同的 std::thread::id