C++异步操作future和aysnc与function和bind
作者:GopherWxf??????? 时间:2023-06-21 06:48:50
异步操作
C++11为异步操作提供了4个接口
std::future : 异步指向某个任务,然后通过future特性去获取任务函数的返回结果。
std::aysnc: 异步运行某个任务函数。
std::packaged_task :将任务和feature绑定在一起的模板,是一种封装对任务的封装。
std::promise:承诺
std::future和std::aysnc 介绍
std::future期待一个函数的返回值,从一个异步调用的角度来说,future更像是执行函数的返回值,C++标准库使用std::future为一次性事件建模,如果一个事件需要等待特定的一次性事件,那么这线程可以获取个future对象来代表这个事件。
异步调用往往不知道何时返回,但是如果异步调用的过程需要同步,或者说后一个异步调用需要使用前一个异步调用的结果。这个时候就要用到future。也就是说,可选择同步,也可选择异步。
future的表现为期望,当前线程持有future时,期望从future获取到想要的结果和返回,可以把future当做异步函数的返回值。
线程可以周期性的在这个future上等待一小段时间,检查future是否已经ready,如果没有,该线程可以先去做另一个任务,一旦future就绪,该future就无法复位(无法再次使用这个future等待这个事件),所以future代表的是一次性事件。
在库的头文件中声明了两种future,唯一future(std::future)和共享future(std::shared_future)这两个是参照std::unique_ptr和std::shared_ptr设立的,前者的实例是仅有的一个指向其关联事件的实例
,而后者可以有多个实例指向同一个关联事件
,当事件就绪时,所有指向同一事件的std::shared_future实例会变成就绪。
跟thread类似,async允许你通过将额外的参数添加到调用中,来将附加参数传递给函数。
如果传入的函数指针是某个类的成员函数,则还需要将类对象指针传入(直接传入,传入指针,或者是std::ref封装)。
默认情况下,std::async是否启动一个新线程,或者在等待future时,任务是否同步运行都取决于你给的参数。这个参数为std::launch类型,async运行某个任务函数,至于异步运行还是同步运行,由这个参数决定
默认选项参数被设置为std::launch::any。如果函数被延迟运行可能永远都不会运行,因为很有可能对应的future没有调用get。
enum class launch{
async,deferred,sync=deferred,any=async|deferred
};
std::launch::async,表明函数会在创建的新线程上运行。
std::launch::defered表明该函数会被延迟调用,直到在future上调用get()或者wait()为止。
std::launch::sync = std::launch::defered,表明该函数会被延迟调用
std::launch::any = std::launch::defered | std::launch::async,表明该函数会被延迟调用,调用时在新线程上运行。
std::future和std::aysnc的使用Demo
这里我们future了两个函数,第一个函数设置为异步,那么在第20行之后,就会创建一个新线程并运行,而不必等待result.get()。第二个函数没有设置参数,那么默认是延迟调用,只有在result2.get()时,才会创建一个新线程并运行。
#include <iostream>
#include <future>
#include <thread>
using namespace std;
int find_result_to_add() {
//std::this_thread::sleep_for(std::chrono::seconds(2)); // 用来测试异步延迟的影响
std::cout << "find_result_to_add" << std::endl;
return 1 + 1;
}
int find_result_to_add2(int a, int b) {
//std::this_thread::sleep_for(std::chrono::seconds(5)); // 用来测试异步延迟的影响
return a + b;
}
void do_other_things() {
std::cout << "do_other_things" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
int main() {
//async异步
std::future<int> result = std::async(std::launch::async,find_result_to_add);
//std::future<decltype (find_result_to_add())> result = std::async(find_result_to_add);
//auto result = std::async(find_result_to_add); // 推荐的写法用aoto
do_other_things();
std::cout << "result: " << result.get() << std::endl; // 延迟是否有影响?
//std::future<decltype (find_result_to_add2(0, 0))> result2 = std::async(find_result_to_add2, 10, 20);
//不写默认any
auto result2=std::async(find_result_to_add2, 10, 20);
std::cout << "result2: " << result2.get() << std::endl; // 延迟是否有影响?
std::cout << "main finish" << endl;
return 0;
}
std::packaged_task 介绍
如果说std::async和std::feature还是分开看的关系的话,那么std::packaged_task就是将任务和feature绑定在一起的模板,是一种封装对任务的封装。
The class template std::packaged_task wraps any Callable target (function, lambda expression, bind expression, or another function object) so that it can be invoked asynchronously. Its return value or exception thrown is stored in a shared state which can be accessed through std::future objects.
可以通过std::packaged_task对象获取任务相关联的feature,调用get_future()方法可以获得std::packaged_task对象绑定的函数的返回值类型的future。std::packaged_task的模板参数是函数签名。( 例如int add(int a, intb)的函数签名就是int(int, int) )
std::packaged_task的使用Demo
#include <iostream>
#include <future>
using namespace std;
int add(int a, int b, int c) {
std::cout << "call add\n";
return a + b + c;
}
void do_other_things() {
std::cout << "do_other_things" << std::endl;
}
int main() {
std::packaged_task<int(int, int, int)> task(add); // 封装任务
do_other_things();
std::future<int> result = task.get_future();
task(1, 1, 2); //必须要让任务执行,否则在get()获取future的值时会一直阻塞
std::cout << "result:" << result.get() << std::endl;
return 0;
}
std::promise 的介绍
std::promise提供了一种设置值
的方式,它可以在这之后通过相关联
的std::future对象进行读取。换种说法,之前已经说过std::future可以读取一个异步函数的返回值了,那么这个std::promise就提供一种方式手动让future就绪
出在promise创建好的时候future也已经创建好了,线程在创建promise的同时会获得一个future,然后将promise传递给设置他的线程,当前线程则持有future,以便随时检查是否可以取值。
promise是一个承诺,当线程创建了promise对象后,这个promise对象向线程承诺他必定会被人设置一个值,和promise相关联的future就是获取其返回的手段。
std::promise的使用Demo
#include <future>
#include <string>
#include <thread>
#include <iostream>
using namespace std;
void print(std::promise<std::string>& p) {
p.set_value("There is the result whitch you want.");
}
void do_some_other_things() {
std::cout << "Hello World" << std::endl;
}
int main() {
std::promise<std::string> promise;
std::future<std::string> result = promise.get_future();
std::thread th(print, std::ref(promise));
do_some_other_things();
std::cout << result.get() << std::endl;
th.join();
return 0;
}
function和bind
在设计回调函数的时候,无可避免地会接触到可回调对象。在C++11中,提供了std::function和std::bind两个方法来对可回调对象进行统一和封装。(回调函数就是一个被作为参数传递的函数)
C++语言中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。和其他对象一样,可调用对象也有类型。例如,每个lambda有它自己唯一的(未命名)类类型;函数及函数指针的类型则由其返回值类型和实参类型决定。
function的用法
头文件:#include <functional>
保存普通函数
//保存普通函数
void func1(int a) {
cout << a << endl;
}
//1. 保存普通函数
std::function<void(int a)> func1_;
func1_ = func1;
func1_(2); //2
保存lambda表达式
//2. 保存lambda表达式
std::function<void()> func2_ = []() {
cout << "hello lambda" << endl;
};
func2_(); //hello world
保存成员函数
//保存成员函数
class A {
public:
A(string name) : name_(name) {}
void func3(int i) const {
cout <<name_ << ", " << i << endl;
}
private:
string name_;
};
//3 保存成员函数
std::function<void(const A&,int)> func3_ = &A::func3;
A a("wxf");
func3_(a, 20);
完整代码:
#include <iostream>
#include <functional>
using namespace std;
//保存普通函数
void func1(int a) {
cout << a << endl;
}
//保存成员函数
class A {
public:
A(string name) : name_(name) {}
void func3(int i) const {
cout <<name_ << ", " << i << endl;
}
private:
string name_;
};
int main() {
cout << "main1 -----------------" << endl;
//1. 保存普通函数
std::function<void(int a)> func1_;
func1_ = func1;
func1_(2); //2
cout << "\n\nmain2 -----------------" << endl;
//2. 保存lambda表达式
std::function<void()> func2_ = []() {
cout << "hello lambda" << endl;
};
func2_(); //hello world
cout << "\n\nmain3 -----------------" << endl;
//3 保存成员函数
std::function<void(const A&,int)> func3_ = &A::func3;
A a("wxf");
func3_(a, 20);
return 0;
}
main1 -----------------
2
main2 -----------------
hello lambda
main3 -----------------
wxf, 20
bind的用法
可将bind函数看作是一个通用的函数适配器
,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表
。调用bind的一般形式:auto newCallable = bind(callable, arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如placeholders::_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:placeholders::_1为newCallable的第一个参数,placeholders::_2为第二个参数,以此类推。
可能看描述还不是很懂,下面来看看代码:
#include <iostream>
#include <functional>
using namespace std;
void fun_1(int x,int y,int z) {
cout<<"fun_1 print unchanged: x=" <<x<<",y="<< y << ",z=" <<z<<endl;
}
void fun_2(int &a,int &b) {
a++;
b++;
cout<<"fun_2 print Increment: a=" <<a<<",b="<<b<<endl;
}
class A {
public:
// 重载fun_3,主要bind的时候需要
// std::bind((void(A::*)(int, int))&A::fun_3
void fun_3(int k,int m) {
cout << "fun_3 a = " << a << cout <<"\t print unchanged: k="<<k<<",m="<<m<<endl;
}
// std::bind((void(A::*)(string))&A::fun_3
void fun_3(string str) {
cout<<"fun_3 print: str="<<str<<endl;
}
int a;
};
int main() {
//f1的类型为 function<void(int, int, int)>
cout << "\n\nstd::bind(fun_1, 1, 2, 3) -----------------\n";
auto f1 = std::bind(fun_1, 1, 2, 3); //表示绑定函数 fun 的第一,二,三个参数值为:1 2 3
f1(); //print: x=1,y=2,z=3
cout << "\n\nstd::bind(fun_1, 10, 20, 30) -----------------\n";
auto f1_1 = std::bind(fun_1, 10, 20, 30); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
f1_1();
cout << "\n\nstd::bind(fun_1, placeholders::_1,placeholders::_2, 3) -----------------\n";
auto f2 = std::bind(fun_1, placeholders::_1, placeholders::_2, 3);
//表示绑定函数 fun_1的第三个参数为 3,而fun_1的第一,二个参数分别由调用 f2 的第一,二个参数指定
f2(1,2);//print: x=1,y=2,z=3
f2(10,21,30); // 传入30也没有用
cout << "\n\nstd::bind(fun_1,placeholders::_2,placeholders::_1,3) -----------------\n";
auto f3 = std::bind(fun_1,placeholders::_2,placeholders::_1,3);
//表示绑定函数 fun_1 的第三个参数为 3,而fun_1的第一,二个参数分别由调用 f3 的第二,一个参数指定
//注意: f2 和 f3 的区别。
f3(1,2);//print: x=2,y=1,z=3
cout << "\n\nstd::bind(fun_2, placeholders::_1, n) -----------------\n";
int m = 2;
int n = 3;
////表示绑定fun_2的第一个参数为n, fun_2的第二个参数由调用f4的第一个参数(_1)指定。
auto f4 = std::bind(fun_2, placeholders::_1, n); //func_2(3,<f4_1>)
f4(m); //print: m=3,n=4
cout<<"m="<<m<<endl;//m=3 说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的,如m
cout<<"n="<<n<<endl;//n=3 说明:bind对于预先绑定的函数参数是通过值传递的,如n
cout << "\n\nstd::bind(&A::fun_3,&a1,40,50) -----------------\n";
A a;
a.a = 10;
//f5的类型为 function<void(int, int)>
auto f5 = std::bind((void(A::*)(int, int))A::fun_3, &a, 40, 50);
f5(10,20);//参数以及写死,传参没用
cout << "\n\nstd::bind(&A::fun_3, &a2,placeholders::_1,placeholders::_2) -----------------\n";
A a2;
a2.a = 20;
//f5的类型为 function<void(int, int)>
auto f6 = std::bind((void(A::*)(int, int))&A::fun_3,&a2,placeholders::_1,placeholders::_2); //使用auto关键字
f6(10,20);//调用a.fun_3(10,20),print: k=10,m=20
cout << "\n\nstd::bind(&A::fun_3,a3,std::placeholders::_1,std::placeholders::_2) -----------------\n";
std::function<void(int,int)> fc = std::bind((void(A::*)(int,int))&A::fun_3, &a,std::placeholders::_1,std::placeholders::_2);
fc(10,20); //调用a.fun_3(10,20) print: k=10,m=20
fc = std::bind((void(A::*)(int, int))&A::fun_3,&a2,std::placeholders::_1,std::placeholders::_2);
cout << "\n\nstd::bind(&A::fun_3,&a1,std::placeholders::_1) -----------------\n";
auto f_str = std::bind((void(A::*)(string))&A::fun_3,a,std::placeholders::_1);
f_str("wxf");
return 0;
}
来源:https://juejin.cn/post/7138324152850579493