C++标准程序库使用future来模拟这类一次性事件:若线程需等待某个特定的一次性事件发生,则会以恰当的方式取得一个future,它代表目标事件。
C++标准程序库有两种future,分别由两个类模板实现,其声明位于标准库的头文件内:独占future(unique future,即std::future<>)和共享future(shared future,即std::shared_future<>)。
因为std::thread没有提供直接回传结果的方法,所以函数模板std::async()应运而生(其声明也位于头文件中)。
使用std::async()按异步方式启动任务。我们从std::async()函数处获得std::future对象(而非std::thread对象),运行的函数一旦完成,其返回值就由该对象最后持有。
若要用到这个值,只需在future对象上调用get(),当前线程就会阻塞,以便future准备妥当并返回该值。
调用std::async()时,它可以接收附加参数,进而传递给任务函数作为其参数,此方式与std::thread的构造函数相同
#include
#include
int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
std::future<int> the_answer=std::async(find_the_answer_to_ltuae);
do_other_stuff();
std::cout<<"The answer is "<<the_answer.get()<<std::endl;
}
std::async()的具体实现会自行决定等待future时,是启动新线程,还是同步执行任务
可以设置根据std::launch类型的参数,其值可以是std::launch::deferred或std::launch::asyn同步还是异步。
std::launch::deferred指定在当前线程上延后调用任务函数,等到在future上调用了wait()或get(),任务函数才会执行
std::launch::asyn指定必须另外开启专属的线程,在其上运行任务函数。
std::launch::deferred | std::launch:: async,表示由std::async()的实现自行选择运行方式。最后这项是参数的默认值。
auto f1=std::async(std::launch::async,func()); ⇽--- ①运行新线程
auto f2=std::async(std::launch::deferred,func()); ⇽--- ②在wait()或get()内部运行任务函数
auto f3=std::async(std::launch::deferred | std::launch::async,func());⇽---
auto f4=std::async(func()); ⇽--- ③交由实现自行选择运行方式
f2.wait(); ⇽--- ④前面②处的任务函数调用被延后,到这里才运行
凭借std::async(),即能简便地把算法拆分成多个子任务,且可并发运行。
使std::future和任务关联并非唯一的方法:运用类模板 std::packaged_task<> 的实例,我们也能将任务包装起来;又或者,利用 std::promise<> 类模板编写代码,显式地异步求值。
std::packaged_task<>对象在执行任务时,会调用关联的函数(或可调用对象),把返回值保存为future的内部数据,并令future准备就绪。
它可作为线程池的构件单元.若一项庞杂的操作能分解为多个子任务,则可把它们分别包装到多个std::packaged_task<>实例之中,再传递给任务调度器或线程池。
std::packaged_task<>是类模板,其模板参数是函数签名(function signature)。具有成员函数get_future(),它返回std::future<>实例,该future的特化类型取决于函数签名所指定的返回值。
许多图形用户界面(Graphical User Interface,GUI)框架都设立了专门的线程,作为更新界面的实际执行者。若别的线程需要更新界面,就必须向它发送消息,由它执行操作。该模式可以运用std::packaged_task实现,如下代码所示。
#include
#include
#include
#include
#include
std::mutex m;
std::deque<std::packaged_task<void()>> tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread() ⇽--- ①
{
while(!gui_shutdown_message_received()) ⇽--- ②
{
get_and_process_gui_message(); ⇽--- ③
std::packaged_task<void()> task;
{
std::lock_guard<std::mutex> lk(m);
if(tasks.empty()) ⇽--- ④
continue;
task=std::move(tasks.front()); ⇽--- ⑤
tasks.pop_front();
}
task(); ⇽--- ⑥
}
}
std::thread gui_bg_thread(gui_thread);
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f)
{
std::packaged_task<void()> task(f); ⇽--- ⑦
std::future<void> res=task.get_future(); ⇽--- ⑧
std::lock_guard<std::mutex> lk(m);
tasks.push_back(std::move(task)); ⇽--- ⑨
return res; ⇽--- ⑩
}
在GUI线程上①,轮询任务队列和待处理的界面消息(如用户的单击)③;若有消息指示界面关闭,则循环终止②。假如任务队列一无所有,则循环继续④;否则,我们就从中取出任务⑤,释放任务队列上的锁,随即运行该任务⑥。在任务完成时,与它关联的future会进入就绪状态。
向任务队列布置任务也很简单。我们依据给定的函数创建新任务,将任务包装在内⑦,并随即通过调用成员函数get_future(),取得与该任务关联的future⑧,然后将任务放入任务队列⑨,接着向post_task_for_gui_thread()的调用者返回future⑩。接下来,有关代码向GUI线程投递消息,假如这些代码需判断任务是否完成,以获取结果进而采取后续操作,那么只要等待future就绪即可;否则,任务的结果不会派上用场,关联的future可被丢弃。
std::async用于创建异步任务,std::packaged_task则将一个可调用对象(包括函数、函数对象、lambda表达式、std::bind表达式、std::function对象)进行包装,以便该任务能被异步调用(即在其他线程中调用)。
二者均可通过std::future对象返回执行结果。二者使用的一个主要差别是:std::packaged_task需要等待执行结果返回,而std::async不必。
有些任务无法以简单的函数调用表达出来,还有一些任务的执行结果可能来自多个部分的代码。需运用第三种方法创建future:借助std::promise显式地异步求值。
std::promise
工作机制:等待数据的线程在future上阻塞,而提供数据的线程利用相配的promise设定关联的值,使future准备就绪。
std::promise具有成员函数get_future() ,它返回std::future实例。。promise的值通过成员函数 set_value() 设置,只要设置好,future即准备就绪,凭借它就能获取该值。
如果std::promise在被销毁时仍未曾设置值,保存的数据则由异常代替。
//利用多个promise在单个线程中处理多个连接
#include
void process_connections(connection_set& connections)
{
while(!done(connections)) ⇽--- ① 函数process_connections()反复循环,若done()返回true则停止
{
for(connection_iterator ⇽--- ② 每轮循环中,程序依次检查各个连接
connection=connections.begin(),end=connections.end();
connection!=end;
++connection)
{
if(connection->has_incoming_data()) ⇽--- ③ 若有数据传入,则接收
{
data_packet data=connection->incoming();
std::promise<payload_type>& p=
connection->get_promise(data.id); ⇽--- ④ 假定传入的数据包本身已含有ID和荷载数据。令每个ID与各std::promise对象一一对应。将其相关值设置为数据包的有效荷载
p.set_value(data.payload);
}
if(connection->has_outgoing_data()) ⇽--- ⑤ 或者,若发送队列中有数据,则向外发送
{
outgoing_packet data=
connection->top_of_outgoing_queue();
connection->send(data.payload);
data.promise.set_value(true); ⇽--- ⑥ 向外发送的数据包取自发送队列,并通过连接发出。只要发送完成,与向外发送数据关联的promise就会被设置为true,示意数据发送成功
}
}
}
}
std::future自身存在限制,它只容许一个线程等待结果。若我们要让多个线程等待同一个目标事件,则需改用std::shared_future。