C++异步操作future和aysnc与function和bind

2022-11-13 16:11:52 future 操作 aysnc

异步操作

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;
}

到此这篇关于C++异步操作future和aysnc与function和bind的文章就介绍到这了,更多相关C++异步操作内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章