std::future
其实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
std::promise
promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。
可以通过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)
promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。
future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。
下面我们来看看官方定义:
定义于头文件 <future> | ||
template< class R > class promise; | (1) | (C++11 起) |
template< class R > class promise<R&>; | (2) | (C++11 起) |
template<> class promise<void>; | (3) | (C++11 起) |
1) 空模板
2) 非 void 特化,用于在线程间交流对象
3) void 特化,用于交流无状态事件
类模板 std::promise 提供存储值或异常的设施,之后通过 std::promise 对象所创建的 std::future 对象异步获得结果。注意 std::promise 只应当使用一次。
每个 promise 与共享状态关联,共享状态含有一些状态信息和可能仍未求值的结果,它求值为值(可能为 void )或求值为异常。promise 可以对共享状态做三件事:
-
使就绪:promise 存储结果或异常于共享状态。标记共享状态为就绪,并解除阻塞任何等待于与该共享状态关联的 future 上的线程。
-
释放:promise 放弃其对共享状态的引用。若这是最后一个这种引用,则销毁共享状态。除非这是 std::async 所创建的未就绪的共享状态,否则此操作不阻塞。
-
抛弃:promise 存储以 std::future_errc::broken_promise 为 error_code 的 std::future_error 类型异常,令共享状态为就绪,然后释放它。
promise 是 promise-future 交流通道的“推”端:存储值于共享状态的操作同步于(定义于 std::memory_order )任何在共享状态上等待的函数(如 std::future::get )的成功返回。其他情况下对共享状态的共时访问可能冲突:例如, std::shared_future::get 的多个调用方必须全都是只读,或提供外部同步。
成员函数
(构造函数) | 构造std::promise对象 (公开成员函数) |
(析构函数) | 析构std::promise对象 (公开成员函数) |
operator= | 赋值共享状态 (公开成员函数) |
swap | 交换二个 promise 对象 (公开成员函数) |
获取结果 | |
get_future | 返回与承诺的结果关联的 future (公开成员函数) |
设置结果 | |
set_value | 设置结果为指定值 (公开成员函数) |
set_value_at_thread_exit | 设置结果为指定值,同时仅在线程退出时分发提醒 (公开成员函数) |
set_exception | 设置结果为指示异常 (公开成员函数) |
set_exception_at_thread_exit | 设置结果为指示异常,同时仅在线程退出时分发提醒 (公开成员函数) |
下面举一个小例子,将promise<int> 用作线程间信号:
#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>
void accumulate(std::vector<int>::iterator first,
std::vector<int>::iterator last,
std::promise<int> accumulate_promise)
{
int sum = std::accumulate(first, last, 0);
accumulate_promise.set_value(sum); // 提醒 future
}
void do_work(std::promise<void> barrier)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
barrier.set_value();
}
int main()
{
// 演示用 promise<int> 在线程间传递结果。
std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };
std::promise<int> accumulate_promise;
std::future<int> accumulate_future = accumulate_promise.get_future();
std::thread work_thread(accumulate, numbers.begin(), numbers.end(),
std::move(accumulate_promise));
// future::get() 将等待直至该 future 拥有合法结果并取得它
// 无需在 get() 前调用 wait()
//accumulate_future.wait(); // 等待结果
std::cout << "result=" << accumulate_future.get() << '\n';
work_thread.join(); // wait for thread completion
// 演示用 promise<void> 在线程间对状态发信号
std::promise<void> barrier;
std::future<void> barrier_future = barrier.get_future();
std::thread new_work_thread(do_work, std::move(barrier));
barrier_future.wait();
new_work_thread.join();
}
输出:
result=21
std::packaged_task
其实std::packaged_task并没有引入新的概念, 它是一个包装类,它包装任何
可调用 (Callable)
目标(函数、 lambda 表达式、 bind 表达式或其他函数对象),使得能异步调用它。其返回值或所抛异常被存储于能通过
std::future
对象访问的共享状态中。
正
如 std::function , std::packaged_task 是多态、具分配器的容器:可在堆上或以提供的分配器分配存储的可调用对象。
std::packaged_task本身很简单,但是却给了我们很多想象空间,这篇文章就不展开讲它的具体应用场景。
下面我们来看一下具体的定义:
定义于头文件 <future> | ||
template< class > class packaged_task; // 不定义 | (1) | (C++11 起) |
template< class R, class ...Args > | (2) | (C++11 起) |
下边是一些比较枯燥的API解释, 不喜欢看可以略过,直接看例子。
成员函数
(构造函数) | 构造任务对象 (公开成员函数) |
(析构函数) | 析构任务对象 (公开成员函数) |
operator= | 移动任务对象 (公开成员函数) |
valid | 检查任务对象是否拥有合法函数 (公开成员函数) |
swap | 交换二个任务对象 (公开成员函数) |
获取结果 | |
get_future | 返回与承诺的结果关联的 std::future (公开成员函数) |
执行 | |
operator() | 执行函数 (公开成员函数) |
make_ready_at_thread_exit | 执行函数,并确保结果仅在一旦当前线程退出时就绪 (公开成员函数) |
reset | 重置状态,抛弃任何先前执行的存储结果 (公开成员函数) |
非成员函数
std::swap(std::packaged_task) (C++11) | 特化 std::swap 算法 (函数模板) |
辅助类
std::uses_allocator<std::packaged_task> (C++11)(C++17 前) | 特化 std::uses_allocator 类型特征 (类模板特化) |
例子:
#include <iostream>
#include <future>
#include <thread>
int main()
{
// 将一个返回值为7的 lambda 表达式封装到 task 中
// std::packaged_task 的模板参数为要封装函数的类型
std::packaged_task<int()> task([](){return 7;});
// 获得 task 的 future
std::future<int> result = task.get_future(); // 在一个线程中执行 task
std::thread(std::move(task)).detach(); std::cout << "Waiting...";
result.wait();
// 输出执行结果
std::cout << "Done!" << std:: endl << "Result is " << result.get() << '\n';
}
例子2:
#include <iostream>
#include <cmath>
#include <thread>
#include <future>
#include <functional>
// 避免对 std::pow 重载集消歧义的独有函数
int f(int x, int y) { return std::pow(x,y); }
void task_lambda()
{
std::packaged_task<int(int,int)> task([](int a, int b) {
return std::pow(a, b);
});
std::future<int> result = task.get_future();
task(2, 9);
std::cout << "task_lambda:\t" << result.get() << '\n';
}
void task_bind()
{
std::packaged_task<int()> task(std::bind(f, 2, 11));
std::future<int> result = task.get_future();
task();
std::cout << "task_bind:\t" << result.get() << '\n';
}
void task_thread()
{
std::packaged_task<int(int,int)> task(f);
std::future<int> result = task.get_future();
std::thread task_td(std::move(task), 2, 10);
task_td.join();
std::cout << "task_thread:\t" << result.get() << '\n';
}
int main()
{
task_lambda();
task_bind();
task_thread();
}
输出:
task_lambda: 512
task_bind: 2048
task_thread: 1024