【问题标题】:Confusion about threads launched by std::async with std::launch::async parameter关于 std::async 与 std::launch::async 参数启动的线程的混淆
【发布时间】:2015-08-28 21:22:43
【问题描述】:

我对@9​​87654321@ 函数有点困惑。

规范说:

“就像在新的执行线程中一样”执行异步操作(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 应该意味着我明确说明,我不想要一个新线程,我只是想确保在需要返回值时调用该函数。

我的假设正确吗?如果不正确,请解释一下。

另外,我知道函数默认调用如下:

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

在这种情况下,我被告知是否会启动新线程取决于实现。再说一遍,这是什么意思?

【问题讨论】:

  • “好像”意味着它理论上可以重用现有线程(例如,在线程池中),只要行为无法区分。实际上,很少有(如果有的话)实现这样做,因为“好像一个新线程”要求您销毁并重新创建所有线程局部变量。
  • @T.C.或实现(重)协程局部变量。让每个线程获得一个默认协程,thread_local 是本地协程。 async 可以创建一个协程,该协程可以放入另一个线程并运行。即,在操作系统提供的线程模型之上模拟线程(使用协程)?
  • 我注意到std::async 坏了,是真的吗?
  • @DavidHaim 异步操作并不意味着“在另一个线程上”。它只是意味着发生时没有与另一个动作同步(在这种情况下,同步是在另一个动作完成时)。这可能意味着在另一个线程上......或其他七十二个东西。
  • @TheFloatingBrain 你从哪里“听到”的?并定义“破碎”。我觉得很好。

标签: c++ multithreading c++11 future stdthread


【解决方案1】:

std::async&lt;future&gt; 标头的一部分)函数模板用于启动(可能)异步任务。它返回一个std::future对象,该对象最终会保存std::async的参数函数的返回值。

当需要该值时,我们在std::future 实例上调用get();这会阻塞线程,直到未来准备好,然后返回值。可以将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 的析构函数必须阻塞,直到异步运行函数的线程完成。因此,std::async 返回的 std::future 实例将阻塞在其析构函数中。

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 返回的 std::future 实例是临时的,并且会阻塞,因为它们的析构函数在 std::async 返回时被正确调用,因为它们没有分配给变量。

std::async 的第一次调用将阻塞 2 秒,然后从对std::async 的第二次调用再阻塞 2 秒。我们可能认为最后一次调用std::async 不会阻塞,因为我们将其返回的std::future 实例存储在一个变量中,但由于这是一个在作用域结束时被销毁的局部变量,它实际上会阻塞当局部变量 f 被销毁时,在函数作用域结束时再增加 2 秒。

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

我用来编写这些笔记的信息来源:

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

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

【讨论】:

  • 澄清一下,如果你在你的例子中将std::async的普通调用更改为auto t1 = std::async(...);,阻塞时间将只有2秒左右,对吧?
  • @AdamHunyadi 如果你指的是总阻塞时间 - 是的,因为三个不同的线程会同时休眠。
  • 谢谢!当我强制异步启动时,我不知道为什么我的调用线程被阻塞了。
【解决方案2】:

我也对此感到困惑,并在 Windows 上进行了快速测试,结果表明异步未来将在操作系统线程池线程上运行。一个简单的应用程序可以证明这一点,在 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

【讨论】:

  • 应该去掉 f1.get() f2.get() f3.get()
  • 这将使此示例中的函数在不同的线程上运行,但如果您修改此示例以生成 100,000 个期货,然后定期中断异步函数,您将在 Visual Studio 线程调试器中看到相同的几个线程正在被重复使用。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2012-07-13
    • 2012-04-01
    • 2012-04-21
    • 2017-06-19
    • 2020-11-19
    • 1970-01-01
    • 2019-02-19
    • 1970-01-01
    • 2021-11-24
    相关资源
    最近更新 更多