C++11异步操作future和aysnc 、function和bind

1,261 阅读6分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

前言

  本文介绍异步操作future和aysnc 与 function和bind

异步操作

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>

  1. 保存普通函数
//保存普通函数
void func1(int a) {
	cout << a << endl;
}
//1. 保存普通函数
std::function<void(int a)> func1_;
func1_ = func1;
func1_(2); //2
  1. 保存lambda表达式
//2. 保存lambda表达式
std::function<void()> func2_ = []() {
	cout << "hello lambda" << endl;
};
func2_(); //hello world
  1. 保存成员函数
//保存成员函数
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;
}

在这里插入图片描述

在这里插入图片描述