C++async、future、packaged_task、promise的使用

对线程又有了深刻的认识,以前写的线程基本都是同步线程,而自从学习了muduo网络库更知道要用异步的思想去处理问题,因为有时候同步是必须的,但有的时候同步会造成本不必要的浪费,所以也适当的用异步操作来代替同步操作,故再次更新一下packaged_task和promise的使用方法。也了解了多线程重载函数要如何bind,packaged_task更新中有提到。

目录

    • async
    • future
    • packaged_task
          • 用法1
          • 用法2
        • 更新
    • std::promise
        • 更新

async

初学多线程的我天真的以为异步和多线程是一样的,直到我看见了std::async这个处理异步的函数(头文件在future里面),我才知道异步和多线程是不一样的。
关于多线程与异步区别生动形象的例子
为什么有了多线程还需要异步操作?身为初学的我想到了这个问题在网上百度了没百度到,好在今天读书的时候又找到了自己想要的答案了。首先要注意到开启一根多线程是不能保存返回值,那么聪明的你肯定也想到函数增加一个引用类型参数,不就解决了嘛,代码如下。这样确实解决问题,但我想真正的工程中,一般不会轻易修改一个函数的参数,一个是变得更难维护,另一个是如果使用的是dll或者lib文件还需要重新编译文件,这会相当的耗时(我还没遇到那么耗时的编译时间,纯属道听途说,平常都是写写自己的小玩具还是蛮期待以后工作中遇到的大工程)。综上所述那当我想要一个函数返回值的时候,我该怎么办才好?答案就是使用async这个函数,专门解决函数返回值带给你个烦恼,这函数老棒了

int future_fun(int &a) {
	cout << "hello future_fun" << endl;
	a = 1;
	return 1;
}
int main() {
	int a = 0;
	thread t1(future_fun,std::ref(a));//多线程传递引用参数必须加
									 //std::ref表示传递引用型
									 //参数
	t1.join();
	cout << a << endl;
	
	return 0;
}

async函数将返回值保存到future中去,当需要返回值的时候,用future的对象调用get()即可获得返回值。有两种方法可以实现,一种是延迟处理(还是一根线程,没有开启新的一根线程),一种是开启一根线程去处理。
默认的情况是开启一根新的线程,我们可以通过线程ID来判断是不是开了一根新的线程,如图所示
修正:看完effective modern C++之后又有了新的理解了,默认的情况不是想象中的那样。future有一个合理调度器,如果发生了超订现象, 系统不保证会在开一根线程的
C++async、future、packaged_task、promise的使用_第1张图片

延迟处理的情况就是这样定义的std::launch::deferred,前面加一个这个样的参数就代表是延迟处理了,如果是延迟处理的话,只有当f调用了wait()或者get()函数,函数才会被调用,否则延迟处理的async异步函数将永不会被处理。wait函数不返回结果,只是等待async结束,类似thread的join函数。那我怎么知道他延迟处理是一根线程的呢,我们可以查看他的线程id,线程id是唯一的,我们可知延迟处理并没有新增一根线程

future<int> f = std::async(std::launch::deferred,future_fun);
int future_fun() {
	cout << "future_fun thread_id:" << std::this_thread::get_id() << endl;
	return 1;
}

int main() {
	
	future<int> f = std::async(std::launch::deferred,future_fun);
	//future f = std::async( future_fun);
	cout << "main thread_id:" << std::this_thread::get_id() << endl;
	cout << f.get() << endl;
	
	return 0;
}

C++async、future、packaged_task、promise的使用_第2张图片

future

很多函数都返回一个future对象,比如async、packaged_task、promise等函数都可以返回future对象。不过要注意的是future的get()函数只能调用一次,如果调用第二次会暴出错误的提示

packaged_task

该类模板调用get_future()可以返回一个future对象。下面让我们看看future的使用。还是很简单的。static函数最大的好处就是不会有命名冲突,暴露一个接口给main函数,剩下全定义成static函数,这样其他cpp文件不能调用当前cpp文件的函数。

用法1

直接用packaged_task对象调用

static int packaged_task_fun2() {
	
	cout << "packaged_task_fun(int& val)" << endl;
	return 2;
}
static void packaged_task_fun() {
	const int& r = 0;
	packaged_task<int()> pt1(packaged_task_fun2);
	pt1();//可以直接调用
	future<int> fu = pt1.get_future();
	cout << fu.get() << endl;
}
void future_run() {
	packaged_task_fun();
}
用法2
static int packaged_task_fun2(const int& r) {
	
	cout << "packaged_task_fun2(int& val)" << endl;
	return 2;
}

static void packaged_task_fun() {
	//packaged_task  封装一个函数给thread然后再通过get_future函数获取future对象
	int a = 2;
	const int& r = a;

	packaged_task<int(const int&)> pt1(packaged_task_fun2);
	//packaged_task对象不支持拷贝,支持移动,在这里使用了引用
	thread t1(std::ref(pt1),std::ref(r));
	t1.join();
	future<int> fu = pt1.get_future();
	cout << fu.get() << endl;
}
/*
*如果不使用引用,那就要提前给fu赋值,不然std::move()之后,
*pt1就失效了
*/
void future_run() {
	packaged_task_fun();
}

更新

package_task是一个不错异步的函数接口,你可以利用占位符先来占用参数,然后等到合适的时机在执行此函数。在绑定重载函数的时候需要强制转换函数指针来确定绑定那个函数。

例如这样的,要调用带两个参数的fun,重载函数利用函数指针来进行绑定。当调用task的时候才开始执行函数,get返回结果,这样就是当你方便的时候在启动这个函数,比async更好的进行异步操作,更可控。

int fun(int ,int&) {
	dxgzg::print("fun(int,int&)...");
	return 22;
}
void fun() {
	dxgzg::print("fun()...");
}
using pf_void = void(*)();
using pf_int = int(*)(int,int&);

inline void packaged_test() {
	function<int(int,int&)> f = std::bind((pf_int)fun,std::placeholders::_1,std::placeholders::_2);
	
	packaged_task<int(int,int&)> task(f);
	
	future<int> ft = task.get_future();
	int a = 2;
	int& r = a;
	task(a,r);// 不调用这个会一直阻塞的
	cout << ft.get();
}
int main(){
	packaged_test();
	return 0;
}

std::promise

promise类也是配合future用的。
promise对象调用get_future的时候,如果没有用set_value()设置值的话,这根线程会一直阻塞在这里,直到promise对象调用了set_value。set_value函数只能调用一次不可以多次调用!

void promise_fun1(promise<int>& p) {
	p.set_value(2);
}
void promise_main() {
	std::promise<int>p;
	promise_fun1(p);
	future<int> f = p.get_future();
	cout << f.get() << endl;
}
void future_run() {
	promise_main();
}

更新

调用set_value才启动,换句话说,当future没得到值的线程就会一直阻塞,从而也保持了一定的同步,是set_value完future才不阻塞,所以promise是线程同步之间操作。

void promise_test(future<vector<int>> f) {
	for(auto val : f.get()) {
		dxgzg::print(val);
	}
	return;
}

int main() {
	vector<int> vec{ 1,2,3 };
	promise<vector<int>> p;
	int a = 2;
	int& r = a;
	thread t(promise_test,p.get_future());
	p.set_value(vec);
	t.join();

	return 0;
}

总结是摘自一篇博客的其余的都是我自己写的,原文更详细总结摘自这篇博客
C++async、future、packaged_task、promise的使用_第3张图片

你可能感兴趣的:(C++并发,C++,c++,多线程)