1 基本用法
1.1引入异步操作类的原因
thread对象,它是C++11中提供异步创建多线程的工具。我们想要从线程中返回异步任务结果,一般需要依靠全局变量;从安全角度看,有些不妥;为此C++11提供了std::future类模板,future对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。
int a = 1;
std::thread thread([a](int b) {
return a + b;
}, 2);
thread.join();
不能获取线程的执行结果。
异步操作类封装了新建线程执行任务,同时获取任务返回值操作。
std::asyanc是std::future的高级封装, 一般我们不会直接使用std::futrue,而是使用对std::future的高级封装std::async。
1.2 异步操作类的分类
<future> 头文件中包含了以下几个类和函数:
- Providers 类:std::promise, std::package_task
- Futures 类:std::future, shared_future.
- Providers 函数:std::async()
- 其他类型:std::future_error, std::future_errc, std::future_status, std::launch.
c++11 异步操作相关的类,主要有std::future、std::promise、std::package_task。
- std::future 作为异步操作结果的传输通道,方便获取线程函数的返回值。
- std::promise 用来包装一个值,将数据和future绑定起来,方便线程赋值。
- std::package_task 用来包装一个可调用对象,将函数和future绑定起来。
1.2.1 std::future
std::future 用来访问异步操作的结果,异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取。future_status 有如下3中状态:
- Deferred ,异步操作还没开始;
- Ready,异步操作已经完成;
- Timeout,异步操作超时。
可以通过查询future的状态,知道异步任务的执行情况。获取future 结果的3中方式:
- get #等待异步操作结束并返回结果
- wait #等待异步操作完成
- wait_for #超时等待返回结果
std::future 与std::shared_future
std::future 与 std::shared_future 来获取异步调用的结果,future 是不可以拷贝的,只能移动,share_future 是可以拷贝的,将future 放到容器中则需要share_future。
std::future 对象是std::async、std::promise、std::packaged_task的底层对象,用来传递其他线程中操作的数据结果。
应用举例:
#include <future>
#include <iostream>
#include <stout/stringify.hpp>
bool is_prime(int x)
{
for (int i=0; i<x; i++)
{
if (x % i == 0)
return false;
}
return true;
}
int main()
{
std::future<bool> fut = std::async(is_prime, 700020007);
std::cout << "please wait";
std::chrono::milliseconds span(100);
while (fut.wait_for(span) != std::future_status::ready)
std::cout << ".";
std::cout << std::endl;
bool ret = fut.get();
std::cout << "final result: " << stringify(ret) << std::endl;
return 0;
}
std::async会首先创建线程执行is_prime(700020007), 任务创建之后,std::async立即返回一个std::future对象。
主线程既可使用std::future::get获取结果,如果调用过程中,任务尚未完成,则主线程阻塞至任务完成。
主线程也可使用std::future::wait_for等待结果返回,wait_for可设置超时时间,如果在超时时间之内任务完成,则返回std::future_status::ready状态;如果在超时时间之内任务尚未完成,则返回std::future_status::timeout状态。
1.3 使用std::promise 和std::future
将std::promise和future 绑定起来,为获取线程函数中的某个值提供便利。std::promise的作用就是提供一个不同线程之间的数据同步机制,它可以存储一个某种类型的值,并将其传递给对应的future, 即使这个future不在同一个线程中也可以安全的访问到这个值。
std::promise提供了在一个线程中设置变量,在另一个线程中获取的能力。对我们在线程间同步数据提供了一种解决方案。
promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。
int a = 1;
std::promise<int> res;
std::future<int> future = res.get_future();//promise 与future绑定
std::thread thread([a](int b, std::promise<int>& res) { //线程函数中为外面传进来的promise赋值
res.set_value(a + b);
}, 2, res);
std::cout << future.get() << std::endl;//线程函数执行完成后可以通过promise 的future 获取该值
举例:
#include <iostream> // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
void print_int(std::future<int>& fut) {
int x = fut.get(); // 获取共享状态的值.
std::cout << "value: " << x << '\n'; // 打印 value: 10.
}
int main ()
{
std::promise<int> prom; // 生成一个 std::promise<int> 对象.
std::future<int> fut = prom.get_future(); // 和 future 关联.
std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.
prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.
t.join();
return 0;
}
输出:
value: 10
详解:
std::promise::get_future 介绍:该函数返回一个与 promise 共享状态相关联的 future 。返回的 future 对象可以访问由 promise 对象设置在共享状态上的值或者某个异常对象。只能从 promise 共享状态获取一个 future 对象。
std::promise::set_value 介绍:设置共享状态的值,此后 promise 的共享状态标志变为 ready。
1.4 使用std::package_task 和std::future
std::package 包装了一个可调用对象的包装类(如function、lambda、bind),将函数和future 绑定起来,以便异步调用。std::promise 保存一个共享状态的值,而 packaged_task 保存的是一个函数。
int a = 1;
std::packaged_task<int(int)> task = [a](int b) {
return a + b;
};
std::future<int> future = task.get_future();
task(2);
std::cout << future.get() << std::endl; // 3
举例:
// packaged_task example
#include <iostream> // std::cout
#include <future> // std::packaged_task, std::future
#include <chrono> // std::chrono::seconds
#include <thread> // std::thread, std::this_thread::sleep_for
// count down taking a second for each value:
int countdown (int from, int to) {
for (int i=from; i!=to; --i) {
std::cout << i << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Lift off!\n";
return from-to;
}
int main ()
{
std::packaged_task<int(int,int)> tsk (countdown); // set up packaged_task
std::future<int> ret = tsk.get_future(); // get future
std::thread th (std::move(tsk),10,0); // spawn thread to count down from 10 to 0
// ...
int value = ret.get(); // wait for the task to finish and get result
std::cout << "The countdown lasted for " << value << " seconds.\n";
th.join();
return 0;
}
输出结果:
10
9
8
7
6
5
4
3
2
1
Lift off!
The countdown lasted for 10 seconds.
1.5 使用 std::async
std::async比std::promise、std::packaged_task和std::thread 更高一层,可以用来直接创建异步的task,异步任务返回的结果保存在future中。当需要获取异步任务的结果时,直接调用future.get() 方法即可,如果不关注异步任务的结果,只是简单地等待任务完成,则调用future.wait() 方法。
std::future<int> future3 = std::async([a](int b) {
return a + b;
}, 2);
std::future<int> future4 = std::async([a](int b) {
return a + b;
}, 3);
std::cout << future3.get() + future4.get() << std::endl; // 3+4=7
std::async
C++11中的std::async是个模板函数。std::async异步调用函数,在某个时候以Args作为参数(可变长参数)调用Fn,无需等待Fn执行完成就可返回,返回结果是个std::future对象。
模板函数 async 异步地运行函数 f (潜在地在可能是线程池一部分的分离线程中),并返回最终将保有该函数调用结果的 std::future 。
std::async的启动策略类型是个枚举类enum class launch,包括:
1. std::launch::async:异步,启动一个新的线程调用Fn,该函数由新线程异步调用,并且将其返回值与共享状态的访问点同步。(在调用async 时开始创建线程)
2. std::launch::deferred:延迟,在访问共享状态时该函数才被调用。对Fn的调用将推迟到返回的std::future的共享状态被访问时(使用std::future的wait或get函数)。(延迟加载方式创建线程,调用async时不创建线程,直到调用future的get或wait时才创建线程)
参数Fn:可以为函数指针、成员指针、任何类型的可移动构造的函数对象(即类定义了operator()的对象)。Fn的返回值或异常存储在共享状态中以供异步的std::future对象检索。
参数Args:传递给Fn调用的参数,它们的类型应是可移动构造的。
返回值:当Fn执行结束时,共享状态的std::future对象准备就绪。std::future的成员函数get检索的值是Fn返回的值。当启动策略采用std::launch::async时,即使从不访问其共享状态,返回的std::future也会链接到被创建线程的末尾。在这种情况下,std::future的析构函数与Fn的返回同步。
std::async的特点:
- std::future可以从异步任务中获取结果,一般与std::async配合使用,std::async用于创建异步任务,实际上就是创建一个线程执行相应任务。
- std::async就是异步编程的高级封装,封装了std::future的操作,基本上可以代替std::thread 的所有事情。
- std::async的操作,其实相当于封装了std::promise、std::packaged_task加上std::thread。
参考文献:
【1】C++11之std::future对象使用说明: https://blog.csdn.net/c_base_jin/article/details/89761718
【2】C++11 使用异步编程std::async和std::future:https://www.cnblogs.com/moodlxs/p/10111601.html
【3】C++11 并发指南四(<future> 详解一 std::promise 介绍):https://www.cnblogs.com/haippy/p/3239248.html
【4】C++ 并发编程实战:http://www.j9p.com/down/527914.html