写点什么

C++ 中的 task based 并发

用户头像
赖猫
关注
发布于: 2021 年 03 月 24 日

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进行设置。



用户头像

赖猫

关注

还未添加个人签名 2020.11.28 加入

纸上得来终觉浅,绝知此事要躬行

评论

发布
暂无评论
C++ 中的 task based 并发