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在被销毁时仍未曾设置值,保存的数据则由异常代替。 令每个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,示意数据发送成功
}
}
}
}
若包装的任务函数在执行时抛出异常,则会代替本应求得的结果,被保存到future内并使其准备就绪。 只要调用get(),该异常就会被再次抛出。 假如我们不想保存值,而想保存异常,就不应调用set_value(),而应调用成员函数set_exception()。 std::future自身存在限制,它只容许一个线程等待结果。 若我们要让多个线程等待同一个目标事件,则需改用std::shared_future。 欢迎分享,转载请注明来源:内存溢出
将异常保存到future
//利用多个promise在单个线程中处理多个连接
#include
评论列表(0条)