这一节可能是C++11最难说明白的一节。
其实future有两个兄弟,一个是std::future, 一个是它大哥std::shared_future。他们的区别就是std::future只支持移动语义,它所引用的共享状态不与另一异步返回对象共享。换成人话就是如果你想再多个线程共享一个future,那么你应该用std::shared_future,换成大白话就是你要是想多个线程等待一个future,那么你应该用std::shared_future。如果还是不明白,文章最后会给一个小例子说明。这篇文章主要讲的是std::future。
同时讲std::future离不开它的承载者和创建者, 也就是std::async , std::packaged_task,std::promise,会在今后的文章一一描述。
首先我们先看std::future是如何定义的(如果感觉枯燥可略过这节):
定义于头文件 <future> | ||
template< class T > class future; | (1) | (C++11 起) |
template< class T > class future<T&>; | (2) | (C++11 起) |
template<> class future<void>; | (3) | (C++11 起) |
类模板 std::future 提供访问异步操作结果的机制:
-
(通过 std::async 、 std::packaged_task 或 std::promise 创建的)异步操作能提供一个 std::future 对象给该异步操作的创建者。
-
然后,异步操作的创建者能用各种方法查询、等待或从 std::future 提取值。若异步操作仍未提供值,则这些方法可能阻塞。
-
异步操作准备好发送结果给创建者时,它能通过修改链接到创建者的 std::future 的共享状态(例如 std::promise::set_value )进行。
成员函数
(构造函数) | 构造 future 对象 (公开成员函数) |
(析构函数) | 析构 future 对象 (公开成员函数) |
operator= | 移动future对象 (公开成员函数) |
share | 从 *this 转移共享状态给 shared_future 并返回它(公开成员函数) |
获取结果 | |
get | 返回结果 (公开成员函数) |
状态 | |
valid | 检查 future 是否拥有共享状态 (公开成员函数) |
wait | 等待结果变得可用 (公开成员函数) |
wait_for | 等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回。(公开成员函数) |
wait_until | 等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回。(公开成员函数) |
std::future 涉及到了并行编程的概念。为了更好的理解,我们先讨论一个我们会经常遇到的场景。现在你要做三件事,一个是炖排骨一个是烧开水,同时你还在追最火的电视剧。那么作为一个正常人,你不会傻傻的先烧开水,等水烧完了炖排骨,等排骨好了再去看电视剧吧。一个正常人大概率会选择炖上排骨烧上水后就去看电视剧,然后不断的查看排骨和水是不是好了。
当然排骨和水的处理方式不一样,烧热水等开了会响,而排骨就要根据个人经验还有排骨当前的状态放入佐料并且控制时间。这就涉及到我们编程中的架构模型,std::future要讨论的就是排骨这种情况,而不是烧水。
在编程的过程中,程序中往往会有一些功能或者很耗时或者很占计算资源,这样的程序我们往往倾向于让它在另一个thread中运行,我们的主thread可以先干其他事情,等干完了其他事情在来看看之前的工作干完了没有,如果干完了,那么拿出结果,如果没干完就等它干完或者只等一会。
下边就举一个例子:
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
int main()
{
std::future<int> future = std::async(std::launch::async, [](){
std::this_thread::sleep_for(std::chrono::seconds(3));
return 8;
});
std::cout << "开始炖排骨...\n";
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 << "一分钟以后排骨还没好\n";
} else if (status == std::future_status::ready) {
std::cout << "排骨好了!\n";
}
} while (status != std::future_status::ready);
std::cout << "result is " << future.get() << '\n';
}
可能的输出:
开始炖排骨...
一分钟以后排骨还没好
一分钟以后排骨还没好
排骨好了!
result is 8
当然在实践中我们不会死等,我们会隔一会看看排骨好没好。
std::shared_future
类模板 std::shared_future 提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future 可复制而且多个 shared_future 对象能指代同一共享状态。
若每个线程通过其自身的 shared_future 对象副本访问,则从多个线程访问同一共享状态是安全的。
shared_future 不是文章的重点,这里仅仅举一个例子说明:
#include <iostream>
#include <future>
#include <chrono>
int main()
{
std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;
std::shared_future<void> ready_future(ready_promise.get_future());
std::chrono::time_point<std::chrono::high_resolution_clock> start;
auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t1_ready_promise.set_value();
ready_future.wait(); // 等待来自 main() 的信号
return std::chrono::high_resolution_clock::now() - start;
};
auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t2_ready_promise.set_value();
ready_future.wait(); // 等待来自 main() 的信号
return std::chrono::high_resolution_clock::now() - start;
};
auto result1 = std::async(std::launch::async, fun1);
auto result2 = std::async(std::launch::async, fun2);
// 等待线程变为就绪
t1_ready_promise.get_future().wait();
t2_ready_promise.get_future().wait();
// 线程已就绪,开始时钟
start = std::chrono::high_resolution_clock::now();
// 向线程发信使之运行
ready_promise.set_value();
std::cout << "Thread 1 received the signal "
<< result1.get().count() << " ms after start\n"
<< "Thread 2 received the signal "
<< result2.get().count() << " ms after start\n";
}
可能的输出:
Thread 1 received the signal 0.072 ms after start
Thread 2 received the signal 0.041 ms after start