Future
在前文C++11笔记-多线程-初识-async()和Future中,初次使用async()和Future来实现一个多线程的demo;
在上一文中讲到,class std::future<>允许你等待线程结束并获取其结果,可以获取到线程函数的返回值或者是一个异常;
这里将在学《C++标准库》时,把关于future的详细内容记录下来,方便以后工作学习使用;
在18.3.2 细说Future中,讲到future用来表现某一操作的成果(outcome):可能是一个返回值或是一个异常,但二者不会同时存在;这份成果被管理于一个shared state内,后者可以被async()或一个packaged_task或一个promise创建处理。这份成果也许尚未存在,因此future持有的也可能是“生成该成果”的每一件必要的东西;
接口
操作 | 效果 |
---|---|
future f | Default 构造函数,建立一个future,带着无效状态(invalid state) |
future f(rv) | Move构造函数,建立一个新的future,状态取自rv,并令rv状态失效 |
f.~future() | 销毁状态也销毁*this |
f = rv | Move assignment;销毁f的旧状态,取rv状态填之,并令rv状态失效 |
f.valid() | 如果f具备有效状态就获得true,然后你才可以调用以下各个成员函数 |
f.get() | 阻塞(block)直到后台操作完成。它会迫使被推迟的线程(deferred thread)同步启动(start synchronously),产出结果(如果有的话)或发出异常,并令其状态失效 |
f.wait() | 阻塞(block)直到后台操作完成。它会迫使被推迟的线程(deferred thread)同步启动(start synchronously) |
f.wait_for(dur) | 阻塞(block)dur时间段,或直到后台操作完成。但被推迟的线程(deferred thread)并不会被强制启动 |
f.wait_until(tp) | 阻塞(block)直至到达时间点tp,或直到后台操作完成。但被推迟的线程(deferred thread)并不会被强制启动 |
f.share() | 产生一个shared_future带有当前状态,并令f的状态失效 |
注意事项
1.如果future是被async()返回且其相关的task推迟,对它调用get()或者wait()会同步启动该task,但是wait_for()和wait_until()都不会令一个被推迟任务(deferred task)启动;
2.future的成果只能被取出一次。因此future可能处于有效(valid)或无效(invalid)状态:有效意味着“某一操作的成果或爆发的异常”尚未被取出;
get()只能被调用一次,因为get()会令future处于无效状态;
面对无效的future,调用其析构函数、move assignment操作符或valid()以外的任何操作,都会导致不可预期的行为。
3.get()的返回值:
情况一 | 如果它是void,get()获得的就是void,也就是之前说的“无物” |
情况二 | 如果future的template参数是个reference类型,get()便返回一个reference指向返回值; |
情况三 | 非上述两种情况,则get()返回返回值的一份copy,或是对返回值进行move assignment动作——取决于返回类型是否支持move assignment语义; |
注意:get()的返回值取决于future<>的特化类型;
4.future既不提供copy构造函数也不提供copy assignment操作符,确保绝不会有两个object共享某一个后台操作的状态(state)。“将某个future object状态搬移至另一个”的唯一办法是:调用move构造函数或move assignment操作符;然而通过使用share_future object 也可以令后台任务的状态被共享,其share()会释出控制权;
5.如果调用析构函数的那个future是某一shared state的最后拥有者,而相关的task已启动但尚未结束,析构函数会造成阻塞(block),直到任务完成;
处理后台异常
如果 std::async 调用的函数抛出异常,那么这个异常会被存储在值的位置,同时 future 变为 ready ,如果调用 get() 会重新抛出存储的异常。
在《C++标准库》18.1节中的处理异常部分中举了一个栗子:启动一个后台任务,该任务使用无限循环持续的分配内存给list 变量v增加新元素,这样线程会出现内存分配异常(bad_alloc);
给出以下代码:
#include <future>
#include <list>
#include <iostream>
#include <exception>
using namespace std;
void task1()
{
list<int> v;
while (true)
{
for (size_t i = 0; i < 1000000; i++)
{
v.push_back(i);
}
cout.put('.').flush();
}
cout << "task 1 over " << endl;
}
int main()
{
cout << "starting 2 task" << endl;
cout << "- task1: process endless loop of memory consuption" << endl;
cout << "- task2: wait for <return> and then for task1" << endl;
auto f1 = async(task1);
//cin.get();
cout << "\nwait for the end of task1:" << endl;
try
{
f1.get();
}
catch (const exception& e)
{
cout << "EXCEPTION: " << e.what() << endl;
}
system("pause");
}
执行后效果:
当async()开始启动task1后,task1函数会有两种情况出现;
一种情况是:好的消息,没有什么特殊的事情发生;
另外一种情况是:坏的消息,“对future调用get()”也能处理异常;事实上,当get()被调用,且后台操作已经(或随后由于异常)而终止,该异常不会在此线程内被处理,而是会再次被传播出去。因此,欲处理后台操作所生的异常,你只需要偕同get()做出“以同步方式调用该操作”所作出的相同动作即可;
这个无限循环迟早会出现异常,该异常会终止线程,因为它未被捕获。Future object会保持这一状态直到get()被调用。搭配get()后这个异常在main()内被进一步传播;
等待wait/wait_for/wait_until
wait
只要对某个future调用wait(),就可以强制启动该future象征的线程并等待这一后台操作终止;
std::future<...> f(std::async(func));
...
f.wait();
wait_for
使用wait_for()并给予一个时间段,就可让“异步、运行中”的操作等待一段有限时间:
std::future<...> f(std::async(func));
...
// 等待func线程10s;
f.wait_for(std::chrono::seconds(10));
wait_until
使用wait_until()就可以等待直至达到某特定时间点;
std::future<...> f(std::async(func));
...
// 等待func线程,直到1分钟后;
f.wait_until(std::system_clock::now() + std::chrono::minutes(1));
wait_for和wait_until并不强制启动线程(如果线程尚未启动的话);
wait_for和wait_until的返回值
不论wait_for和wait_until有以下三种返回值:
1.std::future_status::deferred——如果async()延缓了操作而程序中又完全没有调用wait()或get()(那会强制启动)。这种情况下上述两个函数都会立刻返回;
2.std::future_status::timeout——如果某个操作被异步启动但尚未结束,而waiting又已逾期(对给定的时间段而言);
3.std::future_status::ready——如果操作已经完成;
注意:如果给wait_for或者wait_until传入一个zero时间段或者一个过去的时间点,那么wait_for或wait_until函数仅仅是查询这个后台任务是否已被启动,和/或是否它正在运行中;
例子
《C++标准库》举了一个例子,显示了wait_until的应用场景;
需求:需要计算一个数值,计算时间或长或短,我们必须在某个时间段内获得一个粗精度的结果(quickComputation函数),如果这个时间段内能计算得到高精度的结果(accurateComputation)那就更好了;
int quickComputation();
int accurateComputation();
std::future<int> f;
int bestResultInTime()
{
auto tp = std::chrono::system_clock::now() + std::chrono::minutes(1);
f = std::async(std::launch::async, accurateComputation);
int guess = quickComputation();
std::future_status s = f.wait_until(tp);
if (s == std::future_status::ready)
{
return f.get();
}
else
{
return guess;
}
}
注意:future f不能是声明于bestResultInTime函数内的对象,那样的话若时间太短以至于无法完成accurateComputation(),future析构函数就会阻塞直到异步操作结束,这样bestResultInTime函数就会同时计算出粗精度结果和高精度结果,但返回值不一定是高精度的结果;
总结
async()提供了一种编程环境,让我们有机会并行启动某些“稍后(当get()被调用时)才会用到其结果”的动作。换句话说,如果你有某个独立函数f,你有可能受益于并行机制,做法是在你需要调用f时改而把f传给async(),然后在你需要f的结果时改为“对async()返回的future调用get()”;
future提供了外部对async()启动的线程的联系,可以获取到返回值或者异常,如果没有启动成功还能启动函数线程执行;
文献
1.《C++标准库》第二版;