C++11异步编程(std::async, std::future, std::packaged_task, std::promise)
- 2020 年 2 月 15 日
- 笔记
std::async是一个函数模板,会启动一个异步任务,最终返回一个std::future对象。在之前我们都是通过thread去创建一个子线程,但是如果我们要得到这个子线程所返回的结果,那么可能就需要用全局变量或者引用的方法来得到结果,这样或多或少都会不太方便,那么async这个函数就可以将得到的结果保存在future中,然后通过future来获取想要得到的结果。async比起thread来说可以对线程的创建又有了更好的控制,比如可以延迟创建。下面先介绍一下std::future, std::packaged_task, std::promise。
std::future
std::future是一个类模板,提供了一个访问异步操作的结果的机制。我们可以通过future_status去查询future的三种状态,分别是deferred(还未执行),ready(已经完成),timeout(执行超时),所以我们可以通过这个去查询异步操作的状态。future提供了一些函数比如get(),wait(),wait_for(),一般用get()来获取future所得到的结果,如果异步操作还没有结束,那么会在此等待异步操作的结束,并获取返回的结果。wait()只是在此等待异步操作的结束,并不能获得返回结果。wait_for()超时等待返回结果。
// future<获取的结果类型> 变量名 // async(函数名, 参数) std::future<int> fu = std::async(fun, 1); std::cout << fu.get() << std::endl;
std::packaged_task
std::packaged_task是一个类模板,顾名思义是用来打包的,将一个可调用对象封装起来,然后可以将其的返回值传给future。std::packaged_task<函数返回类型(参数类型)> 变量名(函数名)。下面展示一下std::packaged_task()的简单用法,也可以将函数换成lambda表达式。
#include <iostream> #include <future> #include <thread> int fun(int x) { x++; x *= 10; std::cout << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::seconds(5)); return x; } int main() { std::packaged_task<int(int)> pt(fun); // 将函数打包起来 std::future<int> fu = pt.get_future(); // 并将结果返回给future std::thread t(std::ref(pt), 1); std::cout << fu.get() << std::endl; std::cout << std::this_thread::get_id() << std::endl; t.join(); return 0; }
std::promise
std::promise是一个类模板,它的作用是在不同的线程中实现数据的同步,与future结合使用,也间接实现了future在不同线程间的同步。
#include <iostream> #include <future> #include <thread> int fun(int x, std::promise<int>& p) { x++; x *= 10; p.set_value(x); std::cout << std::this_thread::get_id() << std::endl; return x; } int main() { std::promise<int> p; std::future<int> fu = p.get_future(); // 并将结果返回给future std::thread t(fun, 1, std::ref(p)); std::cout << fu.get() << std::endl; // 当promise还没有值的时候在此等待 std::cout << std::this_thread::get_id() << std::endl; t.join(); return 0; }
promise还有一个函数是set_value_at_thread_exit()这个翻译一下就可以直到它的作用是当在这个线程执行结束的时候才会将future的状态设置为ready,而set_value()则直接将future的状态设置为ready。需要注意的是在使用的过程中不能多次set_value(),也不能多次get_future()和多次get(),因为一个promise对象只能和一个对象相关联,否则就会抛出异常。
std::async
其实这个函数是对上面的对象的一个整合,async先将可调用对象封装起来,然后将其运行结果返回到promise中,这个过程就是一个面向future的一个过程,最终通过future.get()来得到结果。它的实现方法有两种,一种是std::launch::async,这个是直接创建线程,另一种是std::launch::deferred,这个是延迟创建线程(当遇到future.get或者future.wait的时候才会创建线程),这两个参数是std::async的第一个参数,如果没有使用这个两个参数,也就是第一个参数为空的话,那么第一个参数默认为std::launch::async | std::launch::deferred,这个就不可控了,由操作系统根据当时的运行环境来确定是当前创建线程还是延迟创建线程。那么std::async的第二个参数就是可调用对象的名称,第三个参数就是可调用对象的参数。
#include <iostream> #include <future> #include <thread> int fun(int x) { x++; x *= 10; std::cout << std::this_thread::get_id() << std::endl; return x; } int main() { // std::launch::deferred 当执行到fu.get才开始创建线程 std::future<int> fu = std::async(std::launch::deferred, fun, 1); std::cout << fu.get() << std::endl; std::cout << std::this_thread::get_id() << std::endl; return 0; }