C++11 多线程 async、future、packaged_task、promise

本文深入探讨C++11中的并发编程特性,包括std::async、std::future、std::shared_future、std::packaged_task和std::promise的使用方法及场景。通过实例代码,详细解析了如何创建后台任务、获取线程返回值、包装任务和保存值以供跨线程使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

目的:希望线程返回一个结果

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

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

程序通过std::future对象的get()成员函数等待线程执行结束并返回结果;

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

添加内容:std::share_future

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

 

我们还可以额外地向std::async()传递一个参数,该参数类型是std::lunnch类型(枚举类型),达到一些特殊的目的;

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

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

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

b)参数:std::launch::async:在调用async函数的时候就开始创建线程;std::async()函数。

实例代码如下:

#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;
}

2.std::packaged_task

目的:打包任务,把任务包装起来

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

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

实例代码如下:

//一般格式
#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::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果
	
	cout <<result.get() << endl;
	cout << "I love China!" << endl;

	return 0;
}
//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;
}

3.std::promise

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

总结:通过promise保存一个值,在将来某时刻我们通过把一个future绑定到这个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;

	//做其他运算,比如整整花费了5秒钟
	std::chrono::milliseconds dura(5000); //定一个5秒的时间
	std::this_thread::sleep_for(dura);  //休息一定时常

	int result = calc; //保存结果
	tmpp.set_value(result);  //结果保存到了tmpp这个对象中
} 

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();//promise与future绑定,用于获取线程返回值

	std::thread t2(mythread2,std::ref(fu1));
	t2.join(); //等mythread2执行完毕

	cout << "I love China!"  << endl;

	return 0;
}

4.小结

学习的这些东西,到底怎么用,什么时候用?

我们学习这些东西的目的,并不是要把它们都用在咱们自己的实际开发中。

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

为了成长,必须阅读一些高手写的代码,从而快速实现自己代码的积累;我们的技术就会有一个大幅度的提升;

更愿意将学习这些内容的理由解释为:为我们将来能够读懂高手甚至大师写的代码铺路!——

 

注:该文是C++11并发多线程视频教程笔记,详情可学习:https://study.163.com/course/courseMain.htm?courseId=1006067356

<think>嗯,用户现在问的是C++中的packaged_task、get_futurefuture,以及如何用它们简洁地实现多线程。首先,我需要确认用户对这些概念的了解程度。可能用户已经对多线程编程有一些基础,但想更具体地了解这些工具的使用方法。 首先,我需要回忆这三个组件的作用。packaged_task是一个类模板,用于包装可调用对象,比如函数或lambda,使其能异步执行。当packaged_task被调用时,它会存储返回或异常,并通过关联的future来获取结果。get_futurepackaged_task的成员函数,用于获取与之关联的future对象,而future则用于异步获取结果。 接下来,用户想知道如何简洁地利用它们实现多线程。我需要考虑如何将这些组件结合起来,通常的流程是:创建packaged_task,获取future,将任务移动到线程中执行,然后通过future获取结果。可能还需要提到异常处理,因为future可以捕获任务中抛出的异常。 然后,我需要思考用户可能的实际应用场景。例如,用户可能需要执行多个任务并收集结果,或者处理可能抛出异常的任务。这时候,使用packaged_taskfuture的组合可以方便地管理各个线程的结果和异常。 另外,用户可能对线程的启动和管理有疑问。比如,是否应该使用std::thread还是其他线程管理方式。这里需要说明,packaged_task本身需要被调用,通常通过线程或异步策略来执行。同时,需要注意packaged_task只能移动,不能复制,所以在传递时需要使用std::move。 可能还需要比较packaged_task与其他多线程工具,比如std::async的区别。packaged_task提供了更灵活的控制,允许用户手动管理任务的执行,而std::async则自动管理线程的创建和销毁。用户如果希望更精细地控制线程,packaged_task是更好的选择。 另外,用户可能关心如何传递参数给任务,以及如何处理返回。需要给出具体的代码示例,展示如何将参数绑定到packaged_task,并通过future获取结果。 还要注意异常处理的部分,如果在任务中抛出异常,future::get()会重新抛出该异常,因此需要在主线程中捕获并处理。这一点对于编写健壮的多线程程序很重要。 最后,总结这些组件的优势,比如解耦任务执行和结果获取,简化多线程编程中的同步问题。同时,提醒用户注意线程安全和资源管理的问题,避免数据竞争和死锁。 可能用户需要的是一个清晰的步骤说明,结合代码示例,说明每个部分的作用。这样用户能够快速理解并应用到自己的项目中。同时,需要注意代码的正确性和简洁性,确保示例能够直接运行并展示核心概念。</think>在 C++ 中,`packaged_task`、`get_future` 和 `future` 是多线程编程中用于 **任务封装** 和 **异步结果传递** 的核心组件。以下是它们的核心概念和用法: --- ### 一、核心概念 #### 1. **`std::packaged_task`** - **作用**:封装一个可调用对象(函数、Lambda 等),将其执行结果与 `future` 绑定。 - **特点**: - 只能移动(`move`),不可复制。 - 本质是一个 **函数包装器**,可通过 `operator()` 调用。 #### 2. **`get_future()`** - **作用**:通过 `packaged_task` 获取其关联的 `future` 对象。 - **规则**: - 每个 `packaged_task` 只能调用一次 `get_future()`。 #### 3. **`std::future`** - **作用**:异步获取 `packaged_task` 的执行结果(或异常)。 - **核心方法**: - `get()`:阻塞等待结果,只能调用一次。 - `wait()`:阻塞等待任务完成,不取结果。 - `valid()`:检查是否关联有效结果。 --- ### 二、简洁实现多线程的步骤 #### 1. **定义任务** 将需要异步执行的函数或 Lambda 包装为 `packaged_task`: ```cpp // 定义一个返回 int 的任务(参数为 int) std::packaged_task<int(int)> task([](int x) { std::this_thread::sleep_for(std::chrono::seconds(1)); return x * x; // 返回平方 }); ``` #### 2. **获取 future** 通过 `get_future()` 获取结果通道: ```cpp std::future<int> result_future = task.get_future(); ``` #### 3. **启动线程执行任务** 将 `packaged_task` 移动到线程中执行(注意:必须用 `std::move`): ```cpp std::thread worker(std::move(task), 5); // 传递参数 5 worker.detach(); // 或 worker.join() ``` #### 4. **获取异步结果** 通过 `future` 阻塞等待结果: ```cpp int result = result_future.get(); // 阻塞直到任务完成,输出 25 ``` --- ### 三、完整示例代码 ```cpp #include <iostream> #include <future> #include <thread> #include <chrono> int main() { // 1. 封装任务(计算平方) std::packaged_task<int(int)> task([](int x) { std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作 return x * x; }); // 2. 获取 future std::future<int> result_future = task.get_future(); // 3. 启动线程执行任务 std::thread worker(std::move(task), 5); // 传递参数 5 worker.detach(); // 分离线程(或使用 join()) // 4. 等待结果并输出 std::cout << "Result: " << result_future.get() << std::endl; // 输出 25 return 0; } ``` --- ### 四、关键注意事项 1. **异常传递**: 如果任务中抛出异常,`future.get()` 会重新抛出该异常: ```cpp try { result_future.get(); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } ``` 2. **线程安全**: - `future` 的 `get()` 是线程安全的,但只能调用一次。 - 若需要多次访问结果,使用 `std::shared_future`。 3. **性能优化**: - 结合 `std::async` 可简化代码(自动管理线程): ```cpp auto result_future = std::async(std::launch::async, [](int x) { return x*x; }, 5); ``` --- ### 五、与其他工具的对比 | 工具 | 特点 | |---------------------|----------------------------------------------------------------------| | `std::packaged_task` | 手动控制任务执行(需显式启动线程) | | `std::async` | 自动管理线程(通过策略 `std::launch::async` 或 `std::launch::deferred`) | | `std::promise` | 更底层的结果传递(需手动设置或异常) | --- ### 六、典型应用场景 - **任务队列**:将多个 `packaged_task` 放入队列,由线程池消费。 - **超时控制**:通过 `future.wait_for()` 实现超时等待。 - **并行计算**:拆分任务到多个 `packaged_task`,异步收集结果。 通过合理使用 `packaged_task` 和 `future`,可以简化多线程的同步逻辑,实现高效的任务驱动模型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值