20

有人告诉过我好几次,我应该使用参数std::async来触发并忘记类型的任务std::launch::async(所以它最好在新的执行线程上发挥作用)。

在这些陈述的鼓舞下,我想看看std::async与以下内容相比如何:

  • 顺序执行
  • 一个简单的分离std::thread
  • 我简单的异步“实现”

我天真的异步实现如下所示:

template <typename F, typename... Args>
auto myAsync(F&& f, Args&&... args) -> std::future<decltype(f(args...))>
{
    std::packaged_task<decltype(f(args...))()> task(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    auto future = task.get_future();

    std::thread thread(std::move(task));
    thread.detach();

    return future;
}

这里没有什么花哨的,将仿函数与其参数一起打包f成一个,在一个分离的新对象上启动它,然后从任务中返回。std::packaged taskstd::threadstd::future

现在测量执行时间的代码std::chrono::high_resolution_clock

int main(void)
{
    constexpr unsigned short TIMES = 1000;

    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        someTask();
    }
    auto dur = std::chrono::high_resolution_clock::now() - start;

    auto tstart = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        std::thread t(someTask);
        t.detach();
    }
    auto tdur = std::chrono::high_resolution_clock::now() - tstart;

    std::future<void> f;
    auto astart = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        f = std::async(std::launch::async, someTask);
    }
    auto adur = std::chrono::high_resolution_clock::now() - astart;

    auto mastart = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        f = myAsync(someTask);
    }
    auto madur = std::chrono::high_resolution_clock::now() - mastart;

    std::cout << "Simple: " << std::chrono::duration_cast<std::chrono::microseconds>(dur).count() <<
    std::endl << "Threaded: " << std::chrono::duration_cast<std::chrono::microseconds>(tdur).count() <<
    std::endl << "std::sync: " << std::chrono::duration_cast<std::chrono::microseconds>(adur).count() <<
    std::endl << "My async: " << std::chrono::duration_cast<std::chrono::microseconds>(madur).count() << std::endl;

    return EXIT_SUCCESS;
}

哪里someTask()有一个简单的方法,在这里稍等一下,模拟完成的一些工作:

void someTask()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

最后,我的结果:

  • 顺序:1263615
  • 线程:47111
  • 标准::同步:821441
  • 我的异步:30784

谁能解释这些结果?它似乎std::aysnc我的幼稚实现慢得多,或者只是简单而简单的detached std::thread s。为什么会这样?在这些结果之后还有什么理由使用std::async

(请注意,我也用 clang++ 和 g++ 做了这个基准测试,结果非常相似)

更新:

阅读 Dave S 的回答后,我将我的小基准更新如下:

std::future<void> f[TIMES];
auto astart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < TIMES; ++i)
{
    f[i] = std::async(std::launch::async, someTask);
}
auto adur = std::chrono::high_resolution_clock::now() - astart;

所以std::futures 现在没有被破坏 - 并因此加入 - 每次运行。在代码中进行此更改后,std::async会产生与我的 implementation & detached 类似的结果std::thread

4

2 回答 2

19

一个关键区别是 async 返回的未来会在未来被销毁时加入线程,或者在您的情况下,用新值替换。

这意味着它必须执行someTask()和加入线程,这两者都需要时间。您的其他测试都没有这样做,它们只是独立地产生它们。

于 2016-05-21T12:32:05.070 回答
8

sts::async返回一个特殊的std::future. 这个未来有一个~future做一个.wait()

所以你的例子根本不同。慢的实际上是在你的时间里完成任务。快速的只是将任务排队,而忘记如何知道任务已完成。由于让线程持续超过 main 结束的程序的行为是不可预测的,因此应该避免它。

比较任务的正确方法是在生成future时存储结果,并且在计时器结束之前存储结果,.wait()或者.join()在计时器到期之前避免销毁对象。然而,最后一种情况使 sewuential 版本看起来比实际更糟。

在开始下一个测试之前,您确实需要加入/等待,否则您正在从他们的时间中窃取资源。

请注意,移动的期货从源中移除了等待。

于 2016-05-21T12:47:41.603 回答