c++高级编程(第4版).pdf_《C++并发编程实战第2版》第四章:同步并发操作(2/4)

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)>的部分类定义如下所示。

190959ff44d3198e4684e10eeedcbc75.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相关的活动提供自定义消息,如下所示。

d8a764b2c82d26510170783bb6097f14.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::promises)

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

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

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)状态,然后可以用来

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于需要学习并发编程的人员来说,《Java并发编程实战》是一本非常经典的书籍。现在这本书已经改,发布了第二。想要下载《Java并发编程实战 第二》PDF文档,可以在网上进行搜索。为了保证下载到正的电子书,最好选择合法的网站进行下载。如果不清楚哪些是合法的下载网站,可以参考一些知名的IT科技网站,如CSDN、博客园,或直接到出社官网下载。在学习并发编程的过程中,需要深入理解书中的并发编程基础知识,包括线程安全、锁、原子性等概念,学习掌握Java并发包和工具的使用。同时,在实际应用中要注意并发问题的处理,避免产生死锁、性能问题以及其他并发编程常见的问题。《Java并发编程实战 第二》不仅可以帮助读者深入理解并发编程,还能为读者提供丰富的例子和实战经验,是一本非常实用的并发编程技术书籍。 ### 回答2: C++ 并发编程实战是一本关于多线程和并发编程的经典著作,本书已经推出了第二。该书介绍了多线程及其相关技术,包括线程之间的同步、互斥、锁、原子操作等。此外,本书还涵盖了异步编程并发数据结构、并发算法等高级主题。 如果你需要深入了解 C++ 并发编程,这本书就是你的一个不错的选择。该书主要分为三部分:基础知识、高级话题以及案例研究。第一部分主要介绍了多线程编程的概念、理论和实践,讲述了如何并发编程、如何避免竞争条件以及如何确保线程之间的同步。第二部分涵盖了一些高级话题,如锁、原子操作并发算法等内容。最后一部分则是案例研究,该部分通过一个完整的示例来演示并发编程的各个方面。 总之,本书是一本系统全面的 C++ 并发编程教材,适合具备 C++ 编程基础的读者阅读。如果你想深入理解多线程和并发编程,这本书是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值