C++11并发与多线程笔记(9) async、future、packaged_task、promise

C++11并发与多线程笔记(9) async、future、packaged_task、promise

1、std::async、std::future创建后台任务并返回值

std::async: 是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象,这个对象是个类模板。

什么叫"启动一个异步任务 "?就是自动创建一个线程,并开始执行对应的线程入口函数,它返回一个std::future对象,这个std::future对象中就含有线程入口函数所返回的结果 ,我们可以通过调用future对象的成员函数get()来获取结果

"future"将来的意思,也有人称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future中保存着一个值,这个值是在将来的某个时刻能够拿到。

std::future对象的get()成员函数会等待线程执行结束并返回结果 ,拿不到结果它就会一直等待,感觉有点像join()。但是,它是可以获取结果的。

std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::thread 的join()更像。

cpp 复制代码
#include <iostream>
#include <future>
using namespace std;
class A {
public:
	int mythread(int mypar) {
		cout << mypar << endl;
		cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
		std::chrono::milliseconds dura(5000);//定义一个5秒的时间
	    std::this_thread::sleep_for(dura);//创建一个线程并开始执行,绑定关系
	    cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;//卡在这里等待mythread()执行完毕,拿到结果
		return mypar;
	}
};
 
int mythread() {
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
	std::chrono::milliseconds dura(5000);//定义一个5秒的时间
	std::this_thread::sleep_for(dura);//创建一个线程并开始执行,绑定关系
	cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;//卡在这里等待mythread()执行完毕,拿到结果
	return 5;
}
 
 
int main() {
	A a;
	int tmep = 12;
	cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
	std::future<int> result1 = std::async(mythread);
	cout << "continue........" << endl;
	cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果,只能使用一次
	
	//类成员函数
	std::future<int> result2 = std::async(&A::mythread, &a, temp); //第二个参数是对象引用才能保证线程里执行的是同一个对象
	cout << result2.get() << endl;
   //或者result2.wait(); //等待线程返回,本身不返回结果
	cout << "good luck" << endl;
	return 0;
}

我们通过向std::async()传递一个参数 ,该参数是std::launch类型枚举类型),来达到一些特殊的目的:

  1. std::lunch::deferred:
    (defer推迟,延期)表示线程入口函数的调用会被延迟 ,一直到std::future的wait()或者get()函数 被调用时(由主线程调用)才会执行;如果wait()或者get()没有被调用,则不会执行
    重点:实际上根本就没有创建新线程。std::launch::deferred意思时延迟调用,并没有创建新线程,是在主线程中调用的线程入口函数。
cpp 复制代码
#include <iostream>
#include <future>
using namespace std;
class A {
public:
	int mythread(int mypar) {
		cout << mypar << endl;
		cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
		std::chrono::milliseconds dura(5000);
		std::this_thread::sleep_for(dura);
		cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
		return mypar;
	}
};

int main() {
	A a;
	int temp = 12;
	cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
	cout << "continue........" << endl;
	std::future<int> result1 = std::async(std::launch::deferred, &A::mythread, &a, temp);
	cout << result1.get() << endl;
	//或者result2.wait();
	cout << "I love China!" << endl;
	return 0;
}
  1. std::launch::async,在调用async函数的时候就开始创建新线程,不添加标记,默认用的就是默认值是 std::launch::async | std::launch::deferred 标记。
cpp 复制代码
int main() {
	A a;
	int temp = 12;
	cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
	cout << "continue........" << endl;
	std::future<int> result1 = std::async(std::launch::async, &A::mythread, &a, temp);//使用std::launch::async标记
	cout << result1.get() << endl;
	//或者result2.wait();
	cout << "I love China!" << endl;
	return 0;
}
  1. 同时使用std::launch::async和std::lunch::deferred标记,并不能在新线程中延迟调用。

2、std::packaged_task:打包任务,把任务包装起来

类模板,它的模板参数是各种可调用对象,通过packaged_task把各种可调用对象包装起来 ,方便将来作为线程入口函数来调用。

cpp 复制代码
#include <thread>
#include <iostream>
#include <future>
using namespace std;
 
int mythread(int mypar) {
	cout << mypar << endl;
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
	std::chrono::milliseconds dura(5000);
	std::this_thread::sleep_for(dura);
	cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
	return 5;
}
 
int main() {
	cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
	//我们把函数mythread通过packaged_task包装起来
    //参数是一个int,返回值类型是int
    //方法1
    std::packaged_task<int(int)> mypt(mythread);
	std::thread t1(std::ref(mypt), 1);//线程开始执行
	t1.join();//等待线程执行完毕
	std::future<int> result = mypt.get_future(); 
	//std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
	cout << result.get() << endl;
	return 0;
}

可调用对象可由函数换成lambda表达式

cpp 复制代码
int main() {
    //方法2,用lambda表达式
    std::packaged_task<int(int)> mypt([](int mypar){
   	cout << mypar << endl;
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
    cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
	return 5;
    });
    std::thread t1(std::ref(mypt), 1);
	t1.join();
	std::future<int> result = mypt.get_future(); 
	//std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
	cout << result.get() << endl;
	cout << "I love China!" << endl;
	return 0;
}

packaged_task包装起来的可调用对象还可以直接调用,从这个角度来讲,packaged_task对象也是一个可调用对象

lambda的直接调用

cpp 复制代码
int main() {
    //方法2,用lambda表达式
    std::packaged_task<int(int)> mypt([](int mypar){
   	cout << mypar << endl;
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
    cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
	return 5;
    });
    //packaged_task包装起来的可调用对象还可以直接调用,所以从这个角度来讲,pakcaged_task对象,也是一个可调用对象;
    mypt(105);//直接调用,相当于函数调用
    std::future<int> result=mypt.get_future();
    cout<<result.get()<<endl; 
	return 0;
}

包装后存放容器里

cpp 复制代码
vector<std::packaged<int(int)>> mytasks;
int main() {
    //方法2,用lambda表达式
    std::packaged_task<int(int)> mypt([](int mypar){
   	cout << mypar << endl;
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
    cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
	return 5;
    });
	mytasks.push_back(std::move(mypt));//入容器,进去用了移动语义,入进去之后mypt就为空
	std::packaged_task<int(int)>mypt2;
	auto iter=mytask.begin();
	mypt2=std::move(*iter);//移动语义
	mytasks.erase(iter);//删除第一个元素,迭代已经失效了,所以后续代码不可以再使用iter
    mypt2(105);//直接调用,相当于函数调用
    std::future<int> result=mypt2.get_future();
    cout<<result.get()<<endl; 
	return 0;
}

3、std::promise

类模板,我们能够在某个线程中给它赋值 ,然后我们可以在其他线程 中,把这个值取出来

cpp 复制代码
#include <thread>
#include <iostream>
#include <future>
using namespace std;
 
void mythread(std::promise<int> &tmpp, int clac) {
	//做一系列复杂的操作
	clac++;
	clac *=10;
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
	std::chrono::milliseconds dura(5000);
	std::this_thread::sleep_for(dura);
	cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
	int result = clac;//保存结果
	tmp.set_value(result); //结果保存到了tmp这个对象中
	return;
}
 
int main() {
	std::promise<int> myprom;
	std::thread t1(mythread, std::ref(myprom), 180);
	t1.join(); //在这里线程已经执行完了
	std::future<int> fu1 = myprom.get_future(); //promise和future绑定,用于获取线程返回值
	auto result = fu1.get();//get只能调用1次
	cout << "result = " << result << endl;
	cout<<"I love China!"<<endl;
}

总结:通过promise保存一个值,在将来某个时刻我们通过把一个future绑定到这个promise上,来得到绑定的值

使用两个子进程

cpp 复制代码
#include <thread>
#include <iostream>
#include <future>
using namespace std;
 
void mythread(std::promise<int> &tmpp, int clac) {
	//做一系列复杂的操作
	clac++;
	clac *=10;
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
	std::chrono::milliseconds dura(5000);
	std::this_thread::sleep_for(dura);
	cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
	int result = clac;//保存结果
	tmp.set_value(result); //结果保存到了tmp这个对象中
	return;
}
 
void mythread2(std::future<int> &tmpf){
	auto result=tmpf.get();
	cout<<"mythread2.result"<<result<<endl;
	return;
}
 
int main() {
	std::promise<int> myprom;
	std::thread t1(mythread, std::ref(myprom), 180);
	t1.join(); //在这里线程已经执行完了
	std::future<int> fu1 = myprom.get_future(); //promise和future绑定,用于获取线程返回值
	
	std::thread t2(mythread2,std::ref(ful));
	t2.join();//等mythread2线程执行完毕
	
	cout<<"I love China!"<<endl;
}

总结:第一个线程1(t1) 计算了一个结果,结果通过future对象给到第二个线程2(t2)。

注意:使用thread时,必须 join() 或者 detach() 否则程序会报异常

3、小结

我们学习这些东西的目的并不是,要把他们都用到实际开发中。

相反,如果我们能够用最少的东西写出一个稳定的,高效的多线程程序,更值得赞赏。

我们为了成长必须阅读一些高手写的代码,从而实现自己代码的积累;

相关推荐
_OP_CHEN13 小时前
【算法基础篇】(五十七)线性代数之矩阵乘法从入门到实战:手撕模板 + 真题详解
线性代数·算法·矩阵·蓝桥杯·c/c++·矩阵乘法·acm/icpc
天天爱吃肉821813 小时前
【跨界封神|周杰伦×王传福(陶晶莹主持):音乐创作与新能源NVH测试,底层逻辑竟完全同源!(新人必看入行指南)】
python·嵌入式硬件·算法·汽车
im_AMBER13 小时前
Leetcode 114 链表中的下一个更大节点 | 删除排序链表中的重复元素 II
算法·leetcode
EmbedLinX13 小时前
嵌入式之协议解析
linux·网络·c++·笔记·学习
凉、介13 小时前
VMware 三种网络模式(桥接 / NAT / Host-Only)原理与实验解析
c语言·网络·笔记·操作系统·嵌入式·vmware
xhbaitxl13 小时前
算法学习day38-动态规划
学习·算法·动态规划
多恩Stone13 小时前
【3D AICG 系列-6】OmniPart 训练流程梳理
人工智能·pytorch·算法·3d·aigc
Aliex_git13 小时前
跨域请求笔记
前端·网络·笔记·学习
wangjialelele13 小时前
Linux中的进程管理
java·linux·服务器·c语言·c++·个人开发
历程里程碑13 小时前
普通数组----轮转数组
java·数据结构·c++·算法·spring·leetcode·eclipse