C++ 系列
文章目录
前言
编写多线程时,无法避免的要进行线程间数据同步/通信。std::promise 和 std::future 是 C++ 进行单向数据传递的一种方式。future和promise的作用是在不同线程之间传递数据。std::promise 是数据的输入端,std::future 是数据的输出端。
一、std::future是什么?
C++11中提供异步创建多线程的工具。我们想要从线程中返回异步任务结果,一般需要依靠全局变量;从安全角度看,有些不妥;为此C++11提供了std::future类模板,future对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。
future对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果
std::async、std::packaged_task或std::promise能提供一个std::future对象给该异步操作的创建者,异步操作的创建者能用各种方法查询、等待或从 std::future 提取值。
若异步操作仍未提供值,则这些方法可能阻塞。异步操作准备好发送结果给创建者时,通过接口(eg,std::promise::set_value std::future) 修改共享状态的值。
在C++标准库中,有两种“期望”,使用两种类型模板实现,
唯一期望(unique futures,std::future<>) std::future的实例只能与一个指定事件相关联。
共享期望(shared futures)(std::shared_future<>) std::shared_future的实例就能关联多个事件。
二、函数详解
1. std::promise
std::promise本身是一个模版类,通过std::promise可以像数据管道中设置value,异常和相应的通知信息。
如果std::promise设置值或者异常超过一次,那么会抛出std::future_error异常。
std::promise提供了如下接口
get_future() // 返回相应的future
set_value() // 设置一个相应的值
set_exception() // 设置一个异常
set_value_at_thread_exit() // 设置一个值,当线程退出时,promise使数据管道中相应值的状态变为ready
set_exception_at_thread_exit()
2. std::future
std::future本身是一个模版类,且其是不可拷贝对象, 可类比std::unique_ptr, 对其所管理的资源具有独占权。
通过std::future,你可以
1、从std::promise获得相应的值
2、询问std::promise是否将值设置为可用
3、等待std::promise的通知。这个等待可以设置一个相对时间间隔或者一个绝对时间
4、创建一个共享的future(std::shared_future)
std::future提供了如下主要接口
share() // 返回一个std::shared_future
get() // 从数据管道获得一个值或者异常,多次调用会导致程序异常。
valid() // 检查共享状态是否有效,当调用完get接口后,该函数返回false
wait() // 等待结果
wait_for() // 等待结果,这个接口会等待一个时间间隔,其返回值为std::future_status
wait_until() // 等待结果,该接口会等待到一个绝对时间点,返回值为std::future_status
3 std::future_status
future的wait_for和wait_until接口的返回值为std::future_status. 在C++11标准中,该值定义如下
ready: 数据管道中的结果已经ready,也即可用
timeout: 数据管道中的数据不可用,且wait等待时间已经到达
deferred: 接口被延迟执行,也即还未开始
enum class future_status {
ready,
timeout,
deferred
};
4 std::async
将异步操作用std::packaged_task包装起来,然后将异步操作的结果放到std::promise中,这个过程就是创造未来的过程。外面再通过future.get/wait来获取这个未来的结果。
async原型:
template<class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&...args);
fn接收一个可调用对象(仿函数、lambda表达式、类成员函数、普通函数…)作为参数,并且异步或是同步执行他们。
policy指的异步执行还是同步执行,由第一个参数的执行策略决定:
1、std::launch::async 传递的可调用对象异步执行;
2、std::launch::deferred 传递的可调用对象同步执行;
3、std::launch::async | std::launch::deferred 可以异步或是同步,取决于操作系统,我们无法控制;
4、如果我们不指定策略,则相当于(3)。
args指可调用对象的入参
std::packaged_task
利用std::packaged_task包装的可调用对象,在异步执行后可通过std::future获取返回值。
一个模板类,std::packaged_task包装任何可调用目标(函数、lambda表达式、bind表达式、函数对象)以便它可以被异步调用。
std::packaged_task类似于std::function,它的返回值或抛出的异常被自动存储于能通过std::future对象访问的共享状态中。
1、std::packaged_task::get_future(),调用std::packaged_task的get_future成员函数返回std::future对象,其与共享状态关联。调用后std::packaged_task与std::future共享相同的共享状态
2、std::packaged_task::operator(), 调用存储的任务。
3、std::packaged_task::reset() 在保持相同存储的任务的同时,以新的共享状态重置对象。
packaged_task原型:
template <class Fn> explicit packaged_task (Fn&& fn);
std::shared_future
std::shared_future 顾名思义就是多个线程共享一个 std::shared_future,且可以拷贝。可用在一个线程传递数据给多个线程的时候,多个线程在自身的线程空间内通过 std::shared_future 共享一个 future,这是线程安全的。
std::shared_future::get 可以无限次调用,而 std::future::get 仅能调用一次。std::shared_future::get 返回的一定是引用
shared_future 可以通过某个 std::future 对象隐式转换(参见 std::shared_future 的构造函数),或者通过 std::future::share() 显示转换,返回一个 std::shared_future 对象,无论哪种转换,被转换的那个 std::future 对象都会变为 not-valid。
三、代码
std::future与std::promise
#include <iostream>
#include <future>
#include <exception>
#include <thread>
#include <unistd.h>
void setval(std::promise<int>& p ,int a) {
try {
if (a == 0) {
throw std::runtime_error("set value 0 not perimted");
} else {
sleep(5);
p.set_value(a);
}
} catch(...) {
p.set_exception(std::current_exception());
}
}
int CallFuture1() {
std::promise<int> prom;
auto fut = prom.get_future();
std::thread t1(setval, std::ref(prom), 2);
try {
std::cout << "fut get value: " << fut.get() << "\n";
} catch(std::exception& ptr) {
std::cout << ptr.what() << "\n";
}
t1.join();
return 0;
}
std::packaged_task
前面在使用时都是调用 std::promise::set_value 来写入值,有时我们可能想将一个函数的返回值写入。当然,我们可以先调用这个函数得到值再调用 std::promise::set_value,但标准库提供了 std::packaged_task 来代替这种做法。
int CallFuture2() {
auto && task = std::packaged_task<int(int)>([](int a){
sleep(2);
return (42+a);
});
auto &&future = task.get_future();
auto t1 = std::thread(std::move(task),2);
std::cout << future.get() << std::endl;
t1.join();
return 0;
}
int CallFuture2_1() {
auto sleep = []() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 1;
};
std::packaged_task<int()> task(sleep);
auto f = task.get_future();
task();
std::cout << "You can see this after 1 second"<<std::endl;
std::cout << f.get() << std::endl;
return 0;
}
std::async
前面手动创建线程,sync帮创建了线程。
int CallFuture3() {
auto future = std::async([]{
sleep(2);
return 42;
});
std::cout << future.get() << std::endl;
return 0;
}
std::future_status
返回状态有三种可能
int thread_task(std::promise<int>& pro, int i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "thread_task" << i << std::endl;
pro.set_value(i);
return 0;
}
int CallFuture4() {
std::promise<int> pro;
std::thread mythread(thread_task, std::ref(pro), 5);
mythread.join();
std::future<int> fut = pro.get_future();
std::future_status status = fut.wait_for(std::chrono::milliseconds(1000));
if (std::future_status::ready == status){ //线程已成功返回
std::cout<< "ready"<<std::endl;
}
else if (std::future_status::timeout == status){ //wait_for的时间已结束,线程仍未成功返回
std::cout<< "timeout"<<std::endl;
}
else if (std::future_status::deferred == status){ //如果std::async的第一个参数设置为std::launch::deferred,则该线程会直到std::future对象调用wait()或get()时才会执行,这种情况就会满足
std::cout<< "deferred"<<std::endl;
}
std::cout << fut.get() << std::endl;
return 0;
}
using namespace std::chrono;
std::string getData(std::string recvData) {
std::cout << "getData start" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(seconds(5));
return "DB_" + recvData;
}
int CallFuture4_1() {
system_clock::time_point start = system_clock::now();
std::future<std::string> fut = std::async(std::launch::async, getData, "Data");
std::future_status status;
std::string dbData;
do
{
status = fut.wait_for(std::chrono::seconds(1));
switch (status){
case std::future_status::ready:
std::cout << "Ready..." << std::endl;
dbData = fut.get();
std::cout << dbData << std::endl;
break;
case std::future_status::timeout:
std::cout << "timeout..." << std::endl;
break;
case std::future_status::deferred:
std::cout << "deferred..." << std::endl;
break;
default:
break;
}
} while (status != std::future_status::ready);
auto end = system_clock::now();
auto diff = duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
return 0;
}
std::shared_future
int CallFuture5()
{
auto promise = std::promise<int>();
auto t1 = std::thread([&promise]{
sleep(2);
promise.set_value(42);
});
auto shared_future = std::shared_future<int>(promise.get_future());
auto t2 = std::thread([shared_future]{
std::printf("thread 2: %d\n", shared_future.get());
});
std::printf("main: %d\n", shared_future.get());
t1.join();
t2.join();
return 0;
}
int CallFuture5_1 ()
{
std::future<int> fut = std::async([](){return 10;});
std::shared_future<int> shared_fut = fut.share();
// 共享的 future 对象可以被多次访问.
std::cout << "value: " << shared_fut.get() << '\n';
std::cout << "its double: " << shared_fut.get()*2 << '\n';
return 0;
}
总结
通过本文的学习,对future promise有个了一定的认识,希望你能够学而用之。
参考:https://blog.csdn.net/qls315/article/details/125470427