与简单的分离线程相比,为什么std :: async会变慢?

kri*_*pet 18 c++ multithreading asynchronous c++11 stdasync

我已经多次被告知,我应该使用参数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;
}
Run Code Online (Sandbox Code Playgroud)

没有看中这里,包函子fstd::packaged task与它的参数一起,启动它的一个新的std::thread被分离,并用返回std::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;
}
Run Code Online (Sandbox Code Playgroud)

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

void someTask()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
Run Code Online (Sandbox Code Playgroud)

最后,我的结果:

  • 顺序:1263615
  • 螺纹:47111
  • std :: sync:821441
  • 我的异步:30784

谁能解释这些结果?这似乎是std::aysnc很多比我幼稚的做法更慢,或者只是简单明了超脱 std::thread秒.那是为什么?在这些结果之后是否有任何理由使用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;
Run Code Online (Sandbox Code Playgroud)

所以std::futures现在没有被摧毁 - 因此加入 - 每次运行.在代码中进行此更改后,std::async会为我的实现和分离的std::threads 生成类似的结果.

Dav*_*e S 18

一个关键的区别是async返回的未来在未来被破坏时加入线程,或者在你的情况下,用新值替换.

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

  • "*async返回的未来在未来被破坏时加入线程*"AKA:永远不会*使用`std :: async`的主要原因. (5认同)
  • @NicolBolas:除非你真的希望操作在本地完成...... (4认同)
  • @KerrekSB:如果你想要那样,你应该明确地调用`future :: wait`,就像你对其他所有'未来'一样.问题是`async`返回的`future`与*其他`future`对象*的行为不同.这是问题的不一致,而不是行为.这种不一致且非用户可重现的行为使得很容易意外地做错事,就像这里所做的那样. (3认同)

Yak*_*ont 5

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

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

比较任务的正确方法是存储所产生的futuregenersting时,定时器结束之前.wait()/ .join()他们所有,或避免破坏的对象,直到定时器超时后.然而,最后一种情况使得sewuential版本看起来比它更糟糕.

在开始下一次测试之前,您需要加入/等待,否则您将从他们的时间中窃取资源.

请注意,移动的期货会从源中删除等待.