3

多线程07:async、future、packaged_task、promise - D-booker

 1 year ago
source link: https://www.cnblogs.com/D-booker/p/16272045.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

📕async、future、packaged_task、promise

本节内容需要包含头文件:#include <future>

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

①:启用async

  • std::async是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象,这个对象是个类模板。
  • “启动一个异步任务”:就是自动创建一个线程,并开始 执行对应的线程入口函数,它返回一个std::future对象,这个std::future对象中就含有线程入口函数所返回的结果,我们可以通过调用future对象的成员函数get()来获取结果。
  • “future”将来的意思,也有人称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future中保存着一个值,这个值是在将来的某个时刻能够拿到
  • std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()。但是,它是可以获取结果的。并且记得一个future只能调用一次get(无论哪里,都会将数据转移),调用两次程序会崩溃。
  • std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::thread 的join()更像。没有只能调用一次限制,但是调用多也没有效果。
  • 如果不调用future的get()成员函数和wait()成员函数,程序也会在主函数return处一直等待调用子线程结束;
#include <iostream>
#include <future>
using namespace std;
class A {
public:
	int mythread(int data) {
		cout << data << endl;
		return data * 2;
	}
};
 
 
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;
	return 5;
}
 
 
int main() {
	A a;
	int tmp = 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, tmp); //第二个参数是对象引用才能保证线程里执行的是同一个对象
	//cout << result2.get() << endl;//get()只能调用一次,result2不再有结果值,转移的是存的数据类型,这里是int!
   //或者result2.wait();
	cout << "finish all" << endl;
	return 0;
}

②:std::async的第一个参数

  • 参数:std::launch::deferred: 表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行; 那如果wait()或者get()没调用,那么线程没执行!线程没有创建!是在主线程中调用的线程入口函数!
#include <iostream>
#include <future>
using namespace std;
 
int mythread() {
	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;
	std::future<int> result1 = std::async(std::launch::deferred ,mythread);
	cout << "continue........" << endl;
	cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果
	cout << "finish all" << endl;
	return 0;
}

image-20220418110151080
  • 参数:std::launch::async:在调用async函数的时候就开始创建线程;会强制std::async创建新线程,和thread一样。
  • 带上两个参数:std::launch::async |std::launch::deferred 这里这个 |:以为这调用async的行为可能是 创建新线程并立即执行,或者没有创建新线程并且延迟调用result.get()才开始执行任务入口函数,两者居其一。(系统会自行决定是异步(创建新线程)还是同步(不创建新线程)方式运行),默认值就是这个!

二、std::package_task#

  • 目的就是:打包任务,把任务包装起来
  • std::packaged_task是个模板类,它的模板参数是各种可调用对象(函数,函数指针,lambda表达式,bind创建的对象,以及重载了函数调用符的类,有点像function);通过std::packaged_task来把各种可调用对象包装起来,方便将来作为线程入口函数来调用。
  • packaged_task包装起来的可调用对象还可以直接调用,所以从这个角度来讲,packaged_task对象,也是一个可调用对象。
//可调用对象是普通函数
#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
    std::packaged_task<int(int)> mypt(mythread);
	std::thread t1(std::ref(mypt), 1);//传入真引用!不可以用detach模式
	t1.join();
	std::future<int> result = mypt.get_future(); 
	//std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
	cout << result.get() << endl;
   
	return 0;
}

//可调用对象是lambda表达式
int main() {
	cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
    
	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);//传真引用,因为是packege_task,mypt只能再被调用一次
	t1.join();
	std::future<int> result = mypt.get_future(); 
	
	cout << result.get() << endl;
 
	cout << "finish overhead" << endl;
	return 0;
}

image-20220418112915922
//调用对象是lambda表达式,直接调用
int main() {
	cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
    
	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;
	}); 
    
	mypt(1);//也可以在主线程中直接调用,这样就和function的效果一样,但是只能调用一次
	std::future<int> result = mypt.get_future(); 
	cout << result.get() << endl;
 
	cout << "finish overhead" << endl;
	return 0;
}

image-20220418113609731

tip:了解ref

三、promise#

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

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

#include<iostream>
#include<thread>
#include<mutex>
#include<future>
 
using namespace std;
 
void mythread(std::promise<int>&tmpp, int calc)
{
	//做一系列复杂的操作
	calc++;
	calc *= 10;
 
	//做其他运算,比如整整花费了5秒钟
	std::chrono::milliseconds dura(5000); //定一个5秒的时间
	std::this_thread::sleep_for(dura);  //休息一定时常
 
	int result = calc; //保存结果
	tmpp.set_value(result);  //####1.结果保存到了tmpp这个promise对象中
} 
 
void mythread2(std::future<int> &tmpf) 
{
	auto result = tmpf.get();
	cout <<"mythread result = " << result<<endl;
}
 
int main()
{
	std::promise<int> myprom; //声明一个std::promise对象myprom,保存的值类型为int;
	std::thread t1(mythread,std::ref(myprom),180);
	t1.join();
 
	//获取结果值
	std::future<int> fu1 = myprom.get_future();//####2.promise与future绑定,用于获取线程返回值
 
	std::thread t2(mythread2,std::ref(fu1));
	t2.join(); //等mythread2执行完毕
 
	cout << "finish all"  << endl;
 	
    //fu1不再有用,已经为空;已经全部传入mythread2的tmpf中,但是myprom还存着值1810;
	return 0;
}

//对于ref,package_task、future都会移动后原本的变量就没有内容了,对于promise,却还有,要探索一下

再了解async\future\等

四、std::asyn深入理解#

4.1 std::async参数详述#

  • 延迟调用参数 std::launch::deferred【延迟调用】,std::launch::async【强制创建一个线程】;

  • std::async()我们一般不叫创建线程(他能够创建线程),我们一般叫它创建一个异步任务。std::async和std::thread最明显的不同:就是 async 有时候并不创建新线程

①如果用std::launch::deferred 来调用async?

延迟到调用 get() 或者 wait() 时执行,如果不调用就不会执行

②如果用std::launch::async来调用async?

强制这个异步任务在新线程上执行,这意味着,系统必须要创建出新线程来运行入口函数。

③如果同时用 std::launch::async | std::launch::deferred

这里这个 | 意味着async的行为可能是 std::launch::async 创建新线程立即执行, 也可能是 std::launch::deferred 没有创建新线程并且延迟到调用get()执行,由系统根据实际情况来决定采取哪种方案

④不带额外参数 std::async(mythread),只给async 一个入口函数名,此时的系统给的默认值是 std::launch::async | std::launch::deferred 和 ③ 一样,有系统自行决定异步还是同步运行。

4.2 std::async和std::thread()区别#

  • std::thread()如果系统资源紧张可能出现创建线程失败的情况,如果创建线程失败那么程序就可能崩溃,而且不容易拿到函数返回值(不是拿不到)
  • std::async()创建异步任务。可能创建线程也可能不创建线程,并且容易拿到线程入口函数的返回值;

由于系统资源限制:
①如果用std::thread创建的线程太多,则可能创建失败,系统报告异常,崩溃。

②如果用std::async,一般就不会报异常,因为如果系统资源紧张,无法创建新线程的时候,async不加额外参数的调用方式就不会创建新线程。而是在后续调用get()请求结果时执行在这个调用get()的线程上。

如果你强制async一定要创建新线程就要使用 std::launch::async 标记。承受的代价是,系统资源紧张时可能崩溃。

③根据经验,一个程序中线程数量 不宜超过100~200

4.3 async不确定性问题的解决#

让系统自行决定是否创建线程:std::future<int> result = std::async(mythread);问题焦点在于这个写法,任务到底有没有被推迟执行!

如何判断:通过future中wait_for()方法的返回值;详情看下一节内容

多阅读高手代码,多积累,提升自己技术!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK