货比三家:C++ 中的 task based 并发

2

上一篇文章中讲到, 我打算写一篇文章,聊聊asyncpackaged_taskpromise的区别。所以现在我就来填坑了。

TL;DR

  • async:提供最高层次的抽象。如果你不需要控制线程的运行时机,就选这个。
  • packaged_task:抽象层次比async低。如果你需要控制线程的运行时机,且线程执行的结果即目标结果时,选这个。
  • promise:抽象层次最低。当你想在线程中设置目标结果的值,选这个。

如果你想了解得更详细,那就继续往下看吧。

同台竞技

asyncpackaged_taskpromise三者有一个共同点:它们都可以返回一个future对象,用户可以通过这个futureget方法获取最终的结果。

在下面的代码中,分别用这三者实现同样的功能:延时2秒后返回0:

#include <chrono>
#include <future>
#include <iostream>
#include <thread>

int main()
{
    std::packaged_task<int()> task([](){ 
            std::chrono::milliseconds dura( 2000  );
            std::this_thread::sleep_for( dura  );
            return 0; 
            });
    std::future<int> f1 = task.get_future();
    std::thread(std::move(task)).detach();

    std::future<int> f2 = std::async(std::launch::async, [](){ 
            std::chrono::milliseconds dura( 2000  );
            std::this_thread::sleep_for( dura  );
            return 0; 
            });

    std::promise<int> p;
    std::future<int> f3 = p.get_future();
    std::thread([](std::promise<int> p){ 
            std::chrono::milliseconds dura( 2000  );
            std::this_thread::sleep_for( dura  );
            p.set_value(0); 
            },
            std::move(p)).detach();

    std::cout << "Waiting..." << std::flush;
    f1.wait();
    f2.wait();
    f3.wait();
    std::cout << "Done!\nResults are: "
        << f1.get() << " " << f2.get() << " " << f3.get() << "\n";
    return 0;
}

从上面这段代码可以看到,这三者分别工作在不同的抽象层次上。

  1. async层次最高,你只需要给它提供一个函数,它就会返回一个future对象。接下来就只需等待结果了。
  2. packaged_task次之,你在创建了packaged_task后,还要创建一个thread,并把packaged_task交给它执行。
  3. promise就最低了。在创建了thread之后,你还要把对应的promise作为参数传入。这还没完,别忘了在函数中手动设置promise的值。

那么我们的第一个结论就很清晰了:async抽象层次最高,所以除非你需要对并发过程进行细粒度的控制(比如在一些场合下),优先使用async来执行异步任务。

那么什么属于是“一些场合”呢?

async VS. packaged_task and promise

前面已经看到,async会接收一个函数,并返回一个future。在默认情况下,该函数会被就地执行。这也许不是你想要的。通过传递std::launch::defer,可以修改为直到调用future.get才开始执行async中的函数。

即使这样,如果你想把执行函数的时机和获取future对象的时机分离,最好还是放弃用async,而是使用更为底层的packaged_taskpromise

BTW,async有一个古怪的特性,如果你把async返回的future赋值给一个临时变量(或者没管它的返回值),当该变量生命周期结束时,程序会一直阻塞直到async中的函数执行完毕。

{
    std::future<int> tmp = std::async(std::launch::async, [](){ 
            std::chrono::milliseconds dura(VERY_LONG_TIME);
            std::this_thread::sleep_for(dura);
            return 0; 
    });
    // block here for VERY_LONG_TIME
}

这种意料之外的行为会在C++14中被取消掉。所以你用的编译器可能不会遇到这问题。

packaged_task VS. promise

剩下的两个之中怎么选呢?

promise的层次比packaged_task低,所以promise提供给用户的控制粒度也比packaged_task要细。因此,如果你想要更彻底的控制,就选择promise吧。

promise几乎就是future的另一半。对promise调用set_value,就如同对future调用set_value。比起packaged_taskpromise并不在意函数的返回值——毕竟它的值需要手动调用set_value进行设置。

你可能感兴趣的

载入中...