C++之future和promise
future和promise的作用是在不同线程之间传递数据。使用指针也可以完成数据的传递,但是指针非常危险,因为互斥量不能阻止指针的访问;而且指针的方式传递的数据是固定的,如果更改数据类型,那么还需要更改有关的接口,比较麻烦;
promise支持泛型的操作,更加方便编程处理。
在并发编程中,会使用各种回调方法来处理异步返回的结果,如果使用不慎将会让代码分散且难以维护,这里我也是踩了不少坑。既然有坑,就一定有解决办法,C++11的future提供了很好的解决方案,让代码逻辑清晰且易于维护。最近使用C++11中的future比较多,想在这里整理一下这块的知识。
假设线程1需要线程2的数据,那么组合使用方式如下:
线程1初始化一个promise对象和一个future对象,promise传递给线程2,相当于线程2对线程1的一个承诺;
future相当于一个接受一个承诺,用来获取未来线程2传递的值
线程2获取到promise后,需要对这个promise传递有关的数据,之后线程1的future就可以获取数据了。
如果线程1想要获取数据,而线程2未给出数据,则线程1阻塞,直到线程2的数据到达
这也是C++11中的新特性,可以把promise和future当做是在不同线程之间传递值的方式。在某个线程中对promise中生产一个数据,可以在另外一个线程中从future中获取这个数据。
future
和promise
的作用是在不同线程之间传递数据。使用指针也可以完成数据的传递,但是指针非常危险,因为互斥量不能阻止指针的访问;而且指针的方式传递的数据是固定的,如果更改数据类型,那么还需要更改有关的接口,比较麻烦;promise支持泛型的操作,更加方便编程处理。
假设线程1需要线程2的数据,那么组合使用方式如下:
- 线程1初始化一个promise对象和一个future对象,将promise传递给线程2,相当于线程2对线程1的一个承诺;
- future相当于一个接受一个承诺,用来获取未来线程2传递的值
- 线程2获取到promise后,需要对这个promise传递有关的数据,之后线程1的future就可以获取数据了。
- 如果线程1想要获取数据,而线程2未给出数据,则线程1阻塞,直到线程2的数据到达
一个简单的说明流程:
简单的代码示例:
#include <iostream>
#include <functional>
#include <future>
#include <thread>
#include <chrono>
#include <cstdlib>
void thread_set_promise(std::promise<int>& promiseObj) {
std::cout << "In a thread, making data...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
promiseObj.set_value(35);
std::cout << "Finished\n";
}
int main() {
std::promise<int> promiseObj;
std::future<int> futureObj = promiseObj.get_future();
std::thread t(&thread_set_promise, std::ref(promiseObj));
std::cout << futureObj.get() << std::endl;
t.join();
system("pause");
return 0;
}
输出结果:
Prmomise和Future是一种提供访问异步操作结果的机制,可以在线程之间传递数据和异常信息。举个例子,在一家奶茶店里,顾客点了单,服务员给顾客一个排号,当奶茶做好,服务员会更新排号的状态,而顾客则拿着排号继续逛街去了,顾客可以通过查询排号来得知奶茶是否做好,当查到奶茶做好了,顾客就会愉快的回来取奶茶。Promise和Future在其中扮演的则是排号的角色。
上述逻辑,如果用C++代码实现,则是这么一个过程:
#include <iostream> // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
using namespace std;
void WaitForMilkTea(const future<int>& f_notice) {
// 做点别的,比如逛街
int notice = f_notice.get(); // 查看奶茶好了没
cout << "收到通知,回来取奶茶" << endl;
}
void MakeMilkTea(promise<int>* p_notice) {
// 制作奶茶
cout << "奶茶做好了,通知顾客" << endl;
p_notice->set_value(1);
}
int main() {
promise<int> p_notice;
future<int> f_notice = p_notice.get_future(); // future与会通知顾客的promise相关联
thread Customer(WaitForMilkTea, ref(f_notice));
thread Waiter(MakeMilkTea, ref(p_notice));
Waiter.join();
Customer.join();
}
从代码里可以看出std::promise和std:future是<future>的两个类模版,promise对象可以保存某一类型T的值,该值可被与其相关联的future对象所读取。上述略有拗口,意思上也可以这么理解,promise会做出承诺:我将会有一个T类型的值,future是与promise所关联的未来:将来可以从我这里获得承诺的T类型的值。
线程A可以对promise执行set_value(),传入对应产出的值,而另一个线程B则可以使用future的get()方法来获取他们共享的值,但这个线程B会阻塞在那,直到获得future与promise共享的值。这里有一个值得注意的地方:Future的一个重要属性在于它只能被赋值一次。
1. < future >头文件简介
Classes
std::future
std::future_error
std::packaged_task
std::promise
std::shared_future
Functions
std::async
std::future_category
2. std::future
简单来说,std::future提供了一种访问异步操作结果的机制。
从字面意思看,它表示未来。通常一个异步操作我们是不能马上就获取操作结果的,只能在未来某个时候获取。我们可以以同步等待的方式来获取结果,可以通过查询future的状态(future_status)来获取异步操作的结果。future_status有三种状态:
deferred:异步操作还没开始
ready:异步操作已经完成
timeout:异步操作超时
获取future结果有三种方式:get、wait、wait_for,其中get等待异步操作结束并返回结果,wait只是等待异步操作完成,没有返回值,wait_for是超时等待返回结果。
例子:
//查询future的状态
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout << "deferred\n";
} else if (status == std::future_status::timeout) {
std::cout << "timeout\n";
} else if (status == std::future_status::ready) {
std::cout << "ready!\n";
}
} while (status != std::future_status::ready);
3. std::promise
Promise对象可保存T类型的值,该值可被future对象读取(可能在另一个线程中),这是promise提供同步的一种手段。在构造promise时,promise对象可以与共享状态关联起来,这个共享状态可以存储一个T类型或者一个由std::exception派生出的类的值,并可以通过get_future来获取与promise对象关联的对象,调用该函数之后,两个对象共享相同的共享状态(shared state)。
Promise对象是异步provider,它可以在某一时刻设置共享状态的值。
Future对象可以返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标识变为ready,然后才能获取共享状态的值。
例子:
#include <iostream> // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
void print_int(std::future<int>& fut) {
int x = fut.get(); // 获取共享状态的值.
std::cout << "value: " << x << '\n'; // 打印 value: 10.
}
int main ()
{
std::promise<int> prom; // 生成一个 std::promise<int> 对象.
std::future<int> fut = prom.get_future(); // 和 future 关联.
std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.
prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.
t.join();
return 0;
}
std::promise 构造函数
构造函数
default (1) promise();
with allocator (2) template promise (allocator_arg_t aa, const Alloc& alloc);
copy [deleted] (3) promise (const promise&) = delete;
move (4) promise (promise&& x) noexcept;
1.默认构造函数,初始化一个空的共享状态。
2.带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。
3.拷贝构造函数,被禁用。
4.移动构造函数。
另外,std::promise 的 operator= 没有拷贝语义,即 std::promise 普通的赋值操作被禁用,operator= 只有 move 语义,所以 std::promise 对象是禁止拷贝的。
std::promise 成员函数
std::promise::get_future:返回一个与promise共享状态相关联的future对象
std::promise::set_value:设置共享状态的值,此后promise共享状态标识变为ready
std::promise::set_exception:为promise设置异常,此后promise的共享状态标识变为ready
std::promise::set_value_at_thread_exit:设置共享状态的值,但是不将共享状态的标志设置为 ready,当线程退出时该 promise 对象会自动设置为 ready(注意:该线程已设置promise的值,如果在线程结束之后有其他修改共享状态值的操作,会抛出future_error(promise_already_satisfied)异常)
std::promise::swap:交换 promise 的共享状态
4. std::packaged_task
std::packaged_task包装了一个可调用的目标(如function, lambda expression, bind expression, or another function object),以便异步调用,它和promise在某种程度上有点像,promise保存了一个共享状态的值,而packaged_task保存的是一个函数。
std::packaged_task<int()> task([](){ return 7; });
std::thread t1(std::ref(task));
std::future<int> f1 = task.get_future();
auto r1 = f1.get();
5. 小结
Promise,Future 和 Callback常常作为并发编程中一组非阻塞的模型。其中 Future 表示一个可能还没有实际完成的异步任务的【结果】,针对这个结果可以添加 Callback 以便在任务执行成功或失败后做出对应的操作,而 Promise 交由任务执行者,任务执行者通过 Promise 可以标记任务完成或者失败。
6. std::async
std::async大概的工作过程:先将异步操作用std::packaged_task包装起来,然后将异步操作的结果放到std::promise中,这个过程就是创造未来的过程。外面再通过future.get/wait来获取这个未来的结果。
可以说,std::async帮我们将std::future、std::promise和std::packaged_task三者结合了起来。
std::async的原型:
async(std::launch::async | std::launch::deferred, f, args...)
第一个参数是线程的创建策略,默认的策略是立即创建线程:
std::launch::async:在调用async就开始创建线程。
std::launch::deferred:延迟加载方式创建线程。调用async时不创建线程,直到调用了future的get或者wait时才创建线程。
第二个参数是线程函数,后面的参数是线程函数的参数。
简单的例子:
std::future<int> f1 = std::async(std::launch::async, [](){
return 8;
});
cout<<f1.get()<<endl; //output: 8
std::future<int> f2 = std::async(std::launch::async, [](){
cout<<8<<endl;
});
f2.wait(); //output: 8
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)