sonic sairedis get操作顺序异常_《C++并发编程实战第2版》第四章:同步并发操作(2/4)...

本文详细介绍了C++并发编程中如何使用std::packaged_task、std::promise和std::future进行异步任务处理。通过任务与期望的关联,实现任务的异步执行与结果的同步获取。讲解了如何处理任务间的顺序异常,以及通过std::shared_future实现多个线程对同一事件的等待。同时,文章探讨了超时等待的概念,包括基于时长和绝对时间点的等待策略,强调了稳定时钟在超时计算中的重要性。
摘要由CSDN通过智能技术生成

4.2.2 将任务与期望关联起来

std::packaged_task<>将期望绑定到函数或可调用对象。当调用std::packaged_task<>对象时,它调用关联的函数或可调用对象,并让期望就绪(ready),返回值存储为关联的数据。它可以用作线程池(参见第9章)或其他任务管理方案的构建块,比如让每个任务运行在它自己的线程上,或者在一个特定的后台线程上依次运行它们。如果大型操作可以划分为自包含的子任务,那么每个子任务都可以封装在std::packaged_task<>实例中,然后把该实例传递给任务调度器或线程池。这就抽象出了任务的细节;调度器只需要应对std::packaged_task<>实例,而不是单独的函数。

std::packaged_task<>类模板的模板参数是个函数签名,比如void()表示不带参数也没有返回值的函数,或者int(std::string&,double*)表示有一个std::string类型的非const引用和一个指向double类型的指针作为参数,并且返回int的函数。当你构建一个std::packaged_task实例,你必须传递一个函数或者可调用对象,它能接受指定的参数,并且返回类型可以转换成指定的返回类型。类型不必精确匹配;比如你可以从一个带int类型参数并且返回float类型的函数构建一个std::packaged_task<double(double)>,因为这些类型之间都可以隐式转换。

指定函数签名的返回类型确定了从get_future()成员函数返回的std::future<>的类型,而函数签名的参数列表用于指定打包任务(packaged task)的函数调用操作符的签名。例如,std::packaged_task <std::string(std::vector<char>*,int)>的部分类定义如下所示。

1ece98e29aad722ca1a2374079e35a7a.png

因为std::packaged_task对象是一个可调用对象,所以它可以封装在std::function对象中,传给std::thread作为线程函数,或传给需要可调用对象的另一个函数,或直接进行调用。当将std::packaged_task作为函数对象调用时,提供给函数调用操作符的参数被传递给所包含的函数,返回值被存储为异步结果,它可以从get_future()获得的std::future中获取。因此,你可以将任务包装在std::packaged_task中,在将std::packaged_task对象传递到其他地方(以在适当的时候调用它)前检索期望。当你需要结果时,你可以等待期望变成就绪状态。下面的示例展示了这一点。

线程间传递任务

许多图形用户界面(GUI)框架要求从特定的线程更新GUI,因此,如果另一个线程需要更新GUI,它必须向正确的线程发送一条消息来进行更新。std::packaged_task提供了一种实现此目的的方法,而不需要为每个与GUI相关的活动提供自定义消息,如下所示。

803caed43a22f9be78f0d62c5713614c.png

这段代码比较简单:GUI线程①循环直到收到一条关闭GUI的信息后退出②,不断轮询GUI消息来处理③(例如用户点击消息),以及处理任务队列上的任务。如果队列中没有任务④,它将再次循环;否则,它从队列中提取一个任务⑤,然后释放队列上的锁,并执行任务⑥。当任务完成的时候,和任务关联的期望会被变为就绪状态。

将一个任务传入队列也同样简单:从提供的函数⑦创建一个新的打包任务,通过调用get_future()成员函数可以从那个任务上获取期望⑧,并且在期望返回调用者之前⑩,任务被放入到队列(译注:原文是list,应该是笔误)⑨中。然后,如果需要知道任务已经完成,则将消息发布到GUI线程的代码可以等待期望;如果不需要知道的话,则可以丢弃期望。

这个例子使用std::packaged_task<void()>处理任务,它包装了一个不带任何参数并返回void的函数或其他可调用对象 (如果它返回任何其他内容,返回值将被丢弃)。这可能是最简单的任务,但是正如前面所看到的,std::packaged_task也可以用于更复杂的情况——通过指定一个不同的函数签名作为模板参数,你可以改变返回类型(也就是改变了存储在期望关联状态中数据的类型)还有函数调用操作符的参数类型。这个示例可以很容易地扩展为允许在GUI线程上运行的任务接受参数并在std::future中返回一个值,而不仅仅是一个任务完成的指示器。

对于那些不能表示为简单函数调用的任务,或者那些结果可能来自多个地方的任务,该怎么办呢?这些情况由创建期望的第三种方式来处理:使用std::promise来显式设置值。

4.2.3 做出承诺(std::promise)

当你的应用程序需要处理大量的网络连接时,通常倾向于在单独的线程上处理每个连接,因为这可以使网络通信更容易思考和编程。这对于连接数量少(因此线程数量也少)的情况工作的很好。不幸的是,随着连接数量的增加,这种方法就不那么合适了;大量的线程会消耗大量的操作系统资源,并可能导致大量的上下文切换(当线程数量超过可用的硬件并发时),从而影响性能。在极端情况下,操作系统可能会在网络连接能力耗尽之前耗光运行新线程的资源。因此,在具有大量网络连接的应用程序中,通常使用少量线程(可能只有一个)处理连接,让每个线程同时处理多个连接。

考虑其中一个处理连接的线程。数据包将从不同的连接中以本质上随机的顺序进入,同样,数据包也将以随机的顺序排队等待发送。在许多情况下,应用程序的其他部分将等待数据被成功发送,或者等待通过特定的网络连接,新一批数据被成功接收。

std::promise<T>提供一种方法来设定值一个值(类型为T),这个值可以通过关联的std::future<T>对象读取出来。一对std::promise/std::future会为这种设施提供一个可行的机制;等待线程可以阻塞在期望上,同时,提供数据的线程可以使用配对中的承诺(promise)来设置相关的值,并将期望的状态置为就绪(ready)。

通过调用get_future()成员函数,可以获得与给定的std::promise关联的std::future对象,就像std::packaged_task一样。当承诺的值被设置(使用set_value()成员函数)时,期望处于就绪(ready)状态,然后可以用来检索存储的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值