async、future、packaged_task、promise

async、future、packaged_task、promise

视频链接:https://www.bilibili.com/video/BV1Yb411L7ak/?p=11&spm_id_from=333.880.my_history.page.click&vd_source=4c026d3f6b5fac18846e94bc649fd7d0
参考文章:https://blog.csdn.net/weixin_42636062/article/details/120243603

img

提醒

std::move() 是 C++ 标准库中的一个函数模板,定义在 头文件中。它用于对对象进行右值引用的转换,使得可以在适当的情况下使用移动语义。

在 C++11 中引入了移动语义,通过移动语义可以在避免不必要的数据拷贝的同时实现资源的高效转移,例如从一个临时对象或者将要被销毁的对象转移资源到另一个对象。这样可以显著提高程序的性能。

std::move() 的作用是将传入的对象转换为右值引用,从而允许对其进行移动操作。它并不实际移动对象,而只是将对象标记为右值引用,让编译器在适当的情况下选择移动语义而不是拷贝语义。

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

  • std::async是个函数模板,用来启动一个异步任务,启动起来一个异步任务之后(什么叫“启动一个异步任务”,就是自动创建一个线程并开始执行对应的线程入口函数),他返回一个std::future对象,这个std::future对象里面就含有线程函数返回的结果,我们可以通过调用std::future对象的成员函数get()来获取结果;它返回一个std::future对象。

  • std::future是个类模板。有人也称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,在线程执行完毕的时候,你就能拿到结果了,所以大家就这么理解:future对象里会存放一个值,在将来的某个时刻能够拿到;

  • 程序通过std::future对象的get()成员函数等待线程执行结束并返回结果,get()只能调用一次;

  • 这个get()函数很牛,不拿到将来返回的值,誓不罢休,不拿到值,我就卡在那里等待拿值。

  • 添加内容:std::share_future

  • share_future也是个类模板,std::shared_futurestd::future 类似,但是 std::shared_future 可以拷贝、多个 std::shared_future 可以共享某个共享状态的最终结果;即,future的get()函数转移数据,share_futureget()函数复制数据。

#include<iostream>
#include<thread>
#include<list>
#include<string>
#include<mutex>
#include<future>
using namespace std;

int mythread() {
	cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
	chrono::microseconds dura(5000); //5秒时间
	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;
	future<int> result = std::async(mythread);
	cout << "continue...." << endl;
	int def;
	def = 0;
	cout<< result.get() << endl;//因为有一个延迟5秒,如果这个result没有拿到将来(侧面解释了为什么叫future)这个值就会一直卡在这
	cout << "结束" << endl;

}

类成员函数

#include<iostream>
#include<thread>
#include<list>
#include<string>
#include<mutex>
#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; //打印新线程id值
		std::chrono::milliseconds dura(5000); //休息5秒
		std::this_thread::sleep_for(dura); //休息一定时长
		cout << "mythread() end" << " threadid = " << std::this_thread::get_id() << endl;  //打印新线程id值
		return 5;
	}
};


int main() {
	A a;
	int temppar = 12;
	cout << "main" << " threadid = " << std::this_thread::get_id() << endl;
	std::future<int> result = std::async(&A::mythread, &a, temppar);   //第二个参数得是对象引用,才能保证线程里是同一个对象

	return 0;
	
}

视频里没有解释为什么没有get()程序也会等待五秒结束,我问了gpt回答如下:

在第二个代码片段中,虽然没有显式调用result.get()来等待线程完成,但程序似乎仍会等待线程执行完毕。这是因为当std::future对象result超出其作用域(即main()函数的末尾)时,它会自动检查关联的异步操作(即线程)是否已经完成。如果线程在此时尚未完成,std::future析构函数将会阻塞,直到关联的线程完成。

在你的代码中:

int main() {
    A a;
    int temppar = 12;
    cout << "main" << " threadid = " << std::this_thread::get_id() << endl;
    std::future<int> result = std::async(&A::mythread, &a, temppar);
    // 当 'result' 在这里超出作用域时,它会阻塞,直到关联的线程执行完成。

    return 0;
}

当result对象在main()函数结束时超出作用域时,它会阻塞并等待线程执行mythread()的结束。因此,即使你没有显式调用result.get(),程序实际上也会在退出之前等待线程完成。

然而,使用这种行为需要谨慎。如果线程执行时间过长,std::future析构函数可能会导致程序挂起,使其看起来没有响应。通常最好的做法是显式地调用result.get()或使用result.wait()来控制你希望等待线程结果的时机。这样,你可以更优雅地处理潜在的延迟情况。

std::async()参数

  • 参数:std::launch::deferred: 表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行;

    那如果wait()或者get()没有没调用,那么线程会执行吗?没执行,线程没有创建!

    std::launch::deferred:延迟调用,并且没有创建线程,是在主线程中调用的线程入口函数 ;

  • 参数:std::launch::async:在调用async函数的时候就开始创建线程;std::async()函数。就是不用也一样

    用法:std::future result = std::async(std::launch::async, &A::mythread2, &a, tmppar);

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>
#include<future>
using namespace std;

class A
{
public:
	int mythread2(int mypar) //线程入口函数
	{
		cout << mypar << endl;
		cout << "mythread2 start" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id

		std::chrono::milliseconds dura(5000); //定一个5秒的时间
		std::this_thread::sleep_for(dura);  //休息一定时常

		cout << "mythread2 end" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id

		return 5;
	}
};

int mythread() //线程入口函数
{
	cout << "mythread start" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id

	std::chrono::milliseconds dura(5000); //定一个5秒的时间
	std::this_thread::sleep_for(dura);  //休息一定时常

	cout << "mythread end" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id

	return 5;
}
int main()
{
	A a;

	int tmppar = 12;

	cout << "main" << "threadid= " << std::this_thread::get_id() << endl;
	//std::future<int> result = std::async(mythread);//创建一个线程并开始执行,绑定关系,流程并不卡在这里
	std::future<int> result = std::async(std::launch::deferred,&A::mythread2,&a,tmppar); //第二参数是对象引用,才能保证线程里面是同一个对象
	//std::future<int> result = std::async(std::launch::async, &A::mythread2, &a, tmppar);
	cout << "continue....." << endl;

	int def;
	def = 0;
	cout << result.get() << endl;//卡在这里等待mythread()执行完毕,拿到结果;只能调用一次;
	//result.wait();//等待线程返回,本身并不返回结果
	cout << "I love China!" << endl;
	return 0;
}

img

std::packaged_task

std::packaged_task 是 C++ 标准库中的一个类模板,定义在 头文件中。 它用于将一个可调用对象(函数、函数对象或者 Lambda 表达式)与一个 future 关联起来,使得该可调用对象可以在一个异步任务中执行,并且可以通过 std::future 获取其返回值。
std::packaged_task 可以看作是一个异步任务的包装器。它的主要目的是将一个任务(函数或可调用对象)封装起来,使得该任务可以在另一个线程中执行,并且可以通过 std::future 来获取任务的执行结果。

下面是一个简单的示例,演示如何使用 std::packaged_task:

#include <iostream>
#include <future>
#include <functional>

int myTask(int x, int y) {
    return x + y;
}

int main() {
    // 创建一个 packaged_task,将 myTask 与 future 关联起来
    std::packaged_task<int(int, int)> task(myTask);

    // 获取该任务的 future
    std::future<int> result = task.get_future();

    // 启动一个新线程执行该任务
    std::thread threadObj(std::move(task), 10, 20);

    // 等待任务完成,并获取结果
    int sum = result.get();

    // 等待线程执行完毕
    threadObj.join();

    std::cout << "Sum: " << sum << std::endl;

    return 0;
}

在上面的示例中,我们首先创建了一个 std::packaged_task 对象 task,并将 myTask 函数与之关联。然后,我们通过 task.get_future() 获取与该任务关联的 std::future 对象 result,以便在将来获取任务的结果。

接下来,我们启动一个新的线程 threadObj,在该线程中执行任务 task,并传递参数 10 和 20。注意,在启动线程时,我们使用 std::move(task) 将任务转移给线程,因为 std::packaged_task 是不可复制的,只能通过移动语义转移所有权。

最后,我们通过 result.get() 等待任务完成,并从 result 获取任务的结果。此时,主线程会阻塞,直到任务执行完毕,并且可以获取到任务的返回值。最后,我们使用 threadObj.join() 等待线程执行完毕。

通过 std::packaged_task,我们可以很方便地将任务与 std::future 结合起来,实现异步执行任务,并在需要时获取任务的结果。这对于并发编程和任务执行的管理非常有用

视频代码加了下自己的理解

//一般格式
#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>
#include<future>
 
using namespace std;
 
int mythread(int mypar) //线程入口函数
{
	cout << mypar << endl;
	cout << "mythread2 start" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
 
	std::chrono::milliseconds dura(5000); //定一个5秒的时间
	std::this_thread::sleep_for(dura);  //休息一定时常
 
	cout << "mythread2 end" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
 
	return 5;
}
 
int main()
{
	cout << "main" << "threadid= " << std::this_thread::get_id() << endl;
	std::packaged_task<int(int)> mypt(mythread);//我们把函数mythread通过packaged_task包装起来
	std::thread t1(std::ref(mypt),1); //线程直接开始执行,第二个参数作为线程入口函数的参数
	t1.join();
	std::future<int> result = mypt.get_future();//获取 std::packaged_task 的关联 std::future 对象,以便在将来可以通过 result.get() 来获取 std::packaged_task 的执行结果
	
	cout <<result.get() << endl;
	cout << "I love China!" << endl;
 
	return 0;
}

lamdba表达式(示例代码建议多看看懂)

//lamdba表达式作为可调用对象、采用容器
#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>
#include<future>
 
using namespace std;
 
vector < std::packaged_task<int(int)>> mytasks;//容器
 
int main()
{
	cout << "main" << "threadid= " << std::this_thread::get_id() << endl;
 
	std::packaged_task<int(int)> mypt([](int mypar) {
		cout << mypar << endl;
		cout << "mythread2 start" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
		std::chrono::milliseconds dura(5000); //定一个5秒的时间
		std::this_thread::sleep_for(dura);  //休息一定时常
		cout << "mythread2 end" << "threadid= " << std::this_thread::get_id() << endl; //打印线程id
		return 5;
	});
 
 
	mytasks.push_back(std::move(mypt)); //容器,这里用了移动语义,进去之后mypt就为空
 
	std::packaged_task<int(int)> mypt2;
	auto iter = mytasks.begin();
	mypt2 = std::move(*iter); //移动语义
	mytasks.erase(iter); //删除一个元素,迭代器已经失效了,所以后续代码不可以再使用iter;
 
	mypt2(105);//直接调用,相当于函数调用
	std::future<int> result = mypt2.get_future();
	cout << result.get() << endl;
 
	return 0;
}

std::promise

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

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>
#include<future>
using namespace std;

void mythread(std::promise<int>& tmpp, int calc) {
	calc++;
	calc *= 10;
	std::chrono::milliseconds dura(5000); //定一个5秒的时间
	std::this_thread::sleep_for(dura);  //休息一定时常
	int result = calc;
	tmpp.set_value(result);
	return;
}

int main()
{
	
	promise<int> myprom;
	thread t1(mythread, ref(myprom), 180);
	t1.join();
	future<int> fu1 = myprom.get_future();
	auto result = fu1.get();
	cout << "result =" << result << endl;


	cout << "I love China!" << endl;
	return 0;
}

总结

学习不是为了炫技,而是为了写出一个稳定高效的程序,并
能看懂大佬的代码

posted @   NoAcalculia  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示