C++11异步操作——std::future
C++11异步操作
- 1. std::future
- 2. std::async
- 3. std::packaged_task
- 4. std::promise
1. std::future
std::future
是C++11
标准库中的⼀个模板类,它表示⼀个异步操作的结果。当我们在多线程编程中使用异步任务时,std::future
可以帮助我们在需要的时候获取任务的执行结果。std::future
的⼀个重要特性是能够阻塞当前线程,直到异步操作完成,从而确保我们在获取结果时不会遇到未完成的操作。
应用场景
- 异步任务:当我们需要在后台执行⼀些耗时操作时,如网络请求或计算密集型任务等,
std::future
可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率 - 并发任务:在多线程编程中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用
std::future
,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作 - 结果获取:
std::future
提供了⼀种安全的方式来获取异步任务的结果。我们可以使用std::future::get()
函数来获取任务的结果,此函数会阻塞当前线程,直到异步操作完成。这样,在调用get()函数时,我们可以确保已经获取到了所需的结果
这里需要注意的是future
本质上不是一个异步任务,而是一个辅助异步操作的获取任务结果的。所以future
并不能单独使用,而是需要搭配一些能够执行异步操作任务的模板或函数一起使用。
可搭配使用的有:
- std::async函数模板:异步指向一个函数,返回一个
future
对象用于获取结果 - std::packaged_task:类模板:为一个函数生成一个异步任务对象(课调用对象),用于在其他线程中执行
- std::promise:类模板:实例化的对象,可以返回一个对象,在其他线程中向
promise
对象设置数据,其他线程的关联future
就可以获取数据。
2. std::async
std::async
是⼀种将任务与std::future
关联的简单方法。它创建并运行⼀个异步任务,并返回⼀个与该任务结果关联的std::future
对象。默认情况下,std::async是否启动⼀个新线程,或者在等待future时,任务是否同步运⾏都取决于你给的参数。这个参数为std::launch
类型:
- std::launch::deferred 表明该函数会被延迟调用,直到在
future
上调用get()
或者wait()
才会开始执行任务 - std::launch::async 表明函数会在自己创建的线程上运行
- std::launch::deferred | std::launch::async 内部通过系统等条件自动选择策略
使用注意事项:
- 选择合适的启动策略(launch policy):一定要指定一个启动策略,如果没有明确指定启动策略,
std::launch::async
和std::launch::deferred
都不是默认策略,而是undefined
,他是一个任务程序设计,内部有一个调度器(线程池)会根据实际情况决定采用那个策略。 - 保证获取结果(future.get):
std::async
返回一个futrue对象,一定要采用future.get()
或者future.wait()
来获取任务结果或者等待任务完成。如果没有获取结果或者等待任务完成,程序会被视为是不正确的。 - 异常处理:当异步任务出现异常时,这些异常会被
future.get()
捕获,一定要使用future.get()
时准备好捕获和处理可能的异常。 - 确定async对象的声明周期:如果
std::async
获取的std::future
未被移动或者绑定到引用,则完整表达式结尾 - 结果共享:
std::shared_future:
有时候可能需要多个线程访问同一个异步结果,这个时候就可以用到std::shared_future
。它可以让多个线程共享一个异步结果,而不是使用单一的std::futrue
,确保多线程访问时的安全性。
#include <iostream>
#include <future>
// 策略 执行的异步任务 异步任务的参数
int Add(int num1, int num2){
std::cout << "into Add" << std::endl;
return num1 + num2;
}
int main()
{
// std::launch::async策略:内部创建一个线程指向函数,函数运行结果通过future获取
// std:: launch::deferred策略:同步策略,获取结果的时候再去执行函数
// std::future<int> res = std::async(std::launch::async, Add, 1, 2);
std::future<int> res = std::async(std::launch::deferred, Add, 1, 2);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "exe main" << std::endl;
// 主线程休眠一秒中
std::cout << res.get() << std::endl;
return 0;
}
std::launch::async执行结果
into Add
exe main
3
std::launch::deferred执行结果
exe main
into Add
3
3. std::packaged_task
std::packaged_task
就是将任务和std::future
绑定在⼀起的模板,是⼀种对任务的封装。我们可以通过std::packaged_task
对象获取任务相关联的std::future
对象,通过调用get_future()
方法获得。
std::packaged_task
的模板参数是函数签名。可以把std::future和std::async
看成是分开的,而std::packaged_task
则是⼀个整体。
也就是说std::packaged_task
是一个模板类,是一个任务包,是对一个函数模板进行的二次封装,封装成一个可调用的对象作为任务放到线程里去执行。任务包封装好了以后,可以在任意位置进行调用,通过关联的future
来获取执行结果。
指向流程一般是:
- 封装任务
- 指向任务
- 通过future获取执行后的结果,
#include <iostream>
#include <future>
#include <thread>
// 策略 执行的异步任务 异步任务的参数
int Add(int num1, int num2){
std::cout << "into Add" << std::endl;
return num1 + num2;
}
int main()
{
// 1. 封装任务
auto task = std::make_shared<std::packaged_task<int(int,int)>>(Add); // packaged_task底层不允许拷贝构造
// 2. 获取任务包关联的future对象
std::future<int> res = task->get_future();
// 创建新线程执行封装好的任务包
std::thread thr([task](){
(*task)(1, 2);
});
//std::thread(task, 1, 2); //---错误⽤法
//因为packaged_task禁⽌了拷⻉构造,
//且因为每个packaged_task所封装的函数签名都有可能不同,因此也⽆法当作参数⼀样传递
//传引⽤不可取,毕竟任务在多线程下执⾏存在局部变量声明周期的问题,因此不能传引⽤
//因此想要将⼀个packaged_task进⾏异步调⽤,
//简单⽅法就只能是new packaged_task,封装函数传地址进⾏解引⽤调⽤了
//⽽类型不同的问题,在使⽤的时候可以使⽤类型推导来解决
// 3. 获取结果
std::cout << res.get() << std::endl;
thr.join();
return 0;
}
4. std::promise
std::promise
提供了⼀种设置值的方式,它可以在设置之后通过相关联的std::future
对象进行读取。换种说法就是之前说过std::future
可以读取⼀个异步函数的返回值了, 但是要等待就绪,而std::promise
就提供⼀种方式⼿动让 std::future就绪。
一般的执行流程:
- 使用的时候,就是先实例化一个指定结果的
promise
对象 - 通过
promise
对象,获取关联的future
对象 - 在任意位置给
promise
设置数据,就可以通过关联的future
获取这个设置的数据了
#include <iostream>
#include <future>
#include <thread>
// 策略 执行的异步任务 异步任务的参数
int Add(int num1, int num2){
std::cout << "into Add" << std::endl;
return num1 + num2;
}
int main()
{
// 1. 实例化出一个promise对象
std::promise<int> pro;
// 2. 通过promise获取关联的future对象
std::future<int> res = pro.get_future();
// 3. 给promise设置值,后期就可以通过future获取promise中的值
std::thread thr([&pro](){
// 执行函数
int ret = Add(1, 2);
// 将值设置进promise中
pro.set_value(ret);
});
std::cout << res.get() << std::endl;
thr.join();
return 0;
}