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


    前言

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

      本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

    异步操作

    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
    };
    
    • 1
    • 2
    • 3
    • 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 
    #include 
    #include 
    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 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 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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 
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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 
    #include 
    #include 
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    function和bind

      在设计回调函数的时候,无可避免地会接触到可回调对象。在C++11中,提供了std::function和std::bind两个方法来对可回调对象进行统一和封装。(回调函数就是一个被作为参数传递的函数)

      C++语言中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。和其他对象一样,可调用对象也有类型。例如,每个lambda有它自己唯一的(未命名)类类型;函数及函数指针的类型则由其返回值类型和实参类型决定。

    function的用法

    头文件:#include

    1. 保存普通函数
    //保存普通函数
    void func1(int a) {
    	cout << a << endl;
    }
    //1. 保存普通函数
    std::function<void(int a)> func1_;
    func1_ = func1;
    func1_(2); //2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 保存lambda表达式
    //2. 保存lambda表达式
    std::function<void()> func2_ = []() {
    	cout << "hello lambda" << endl;
    };
    func2_(); //hello world
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    完整代码

    #include 
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    main1 -----------------
    2
    
    
    main2 -----------------
    hello lambda
    
    
    main3 -----------------
    wxf, 20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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 
    #include 
    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
    	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(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
    	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
    	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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    安装特定指定版本 低版本的r包 r包降级
    ThreeJS-3D教学二基础形状展示
    【LabVIEW学习】2.for,while,事件
    MySQL 事务的底层原理和 MVCC(二)
    SSE图像算法优化系列三十一:RGB2HSL/RGB2HSV及HSL2RGB/HSV2RGB的指令集优化-上。
    Axure Cloud如何给每个原型配置私有域名
    机器学习模型常用评价指标(Accuracy, Precision, Recall、F1-score、MSE、RMSE、MAE、R方)
    R语言ggplot2可视化:基于aes函数中的fill参数和shape参数自定义绘制分组折线图并添加数据点(散点)、设置可视化图像的主题为theme_dark
    搜索技术【广度优先搜索】 - 双向广度优先搜索 【HDU No. 3085】魔鬼II Nightmare Ⅱ
    服务器优化
  • 原文地址:https://blog.csdn.net/qq_42956653/article/details/126643064