讨喜的隔离可变性(十三)角色的特性

基于角色的并发模型降低了隔离可变性编程的难度,但该模型在适用场景上还是存在一些限制。

由于角色是通过消息来进行彼此间通信的,所以在那些没有强制不可变性的语言中,我们就必须人工来保证消息都是不可变的。传递可变消息将导致线程安全问题并最终使整个应用陷入共享可变性的险境当中,所以当手头的辅助工具还没有发展到可以帮助我们自动查验消息的不可变性之前,保证消息不可变性的重担暂时还是得由我们程序员来肩负。

角色都是各自异步运行的,彼此之前可以通过传递消息来进行协作。但某些角色的意外失败有可能导致其他角色饿死——一个或多个角色可能会一直等待某些关键的协作消息,而这些消息可能由于本应发出该消息的角色失败而永远无法抵达。因此,我们需要在编码时更加谨慎,在角色中加入异常情况的处理逻辑,并将错误消息传播给那些等待响应的角色。

当两个或多个角色互相等待对方发来的消息时,则所有角色都将陷入死锁。我们必须在设计层面谨小慎微,以确保角色在协作过程中不会陷入死锁状态。与此同时,我们应该使用超时来保证程序不会由于协作环节中个别角色失败无法响应而导致其他角色无限期的等待。

角色一次只能处理一个消息请求,所以无论是动作消息还是请求某个响应或状态的消息,在角色内部都是顺序处理的。对于那些只读任务而言,这种做法可能会降低程序整体的并发度。所以我们最好还是用粗粒度的消息来设计应用程序。此外,我们还可以通过设计单向的“发送并遗忘”类型的消息代替双向消息来减少角色之间的相互等待。

并非所有的应用程序都适合用基于角色的模型实现。只有在待解决的任务可以被拆分成多个彼此相互独立的子任务并且子任务之间只有少量交互的情况下,使用角色才是合适的。但如果子任务之间需要频繁交互或者子任务们需要通过形成一个裁决集(quorum)的形式来进行协作,则使用基于角色的模型就是不合适的。而对于这类问题,我们可以尝试将基于角色的模型与其他并发模型混搭或干脆考虑重新设计。

小结

角色是单线程的,彼此间通过传递消息来进行交互。通过本章的学习,我们了解到角色有如下特性:

  • 降低了隔离可变性的使用门槛
  • 从根本上消除了同步问题
  • 提供了高效的单向消息,但同时也提供了不怎么高效的发送并等待功能
  • 可扩展性非常强;同时由于角色都是单线程的,所以我们可以很方便地用线程池来对其进行管理
  • 允许我们发送消息,但同时也通过接口的方式支持类型化版本(在Akka中)。
  • 允许我们通过事务来完成多角色之间的协作

虽然角色为我们提供了一种强大的编程模型,但该模型在某些方面仍有一些限制。例如,如果我们在使用角色进行设计的时候没有考虑周到,则程序可能存在角色饿死或死锁的潜在风险。此外,我们还需要保证消息的不可变性,这一点在没有语言层面的支持的环境中尤为重要。

下一章我们将学习如何在各种JVM支持的语言中使用角色。 

时间: 2024-10-01 02:25:48

讨喜的隔离可变性(十三)角色的特性的相关文章

讨喜的隔离可变性(二)角色的特性

角色是一种能够接收消息.处理请求以及发送响应的自由运行的活动(activity),主要被设计用来支持异步化且高效的消息传递机制. 每个角色都有一个内建的消息队列,该队列与手机上所使用的短信队列十分相似.假设Sally和Sean同时给Bob的手机发了短信,则运营商将会把这两条短信都保存起来以便Bob在方便的时候取走.类似地,基于角色的并发库允许多个角色并发地发送消息.默认情况下,消息发送者都是非阻塞的:它们会先把消息发送出去,然后再继续处理自己的业务逻辑.类库一般会让特定的角色顺序地拾取并处理消息

讨喜的隔离可变性-前言

曾有个的医嘱是这样说的:"如果它伤到了你,那就别再用它了".在并发编程领域,共享可变性就是那个"它". 虽然JDK的线程API使我们可以非常容易地创建线程,但如何防止线程冲突和逻辑混乱却又成了大问题.STM虽然可以解决部分问题,但是在一些类似Java这样的语言中,我们仍不得不非常小心谨慎地避免非托管可变变量和事务逻辑中产生某些副作用.而令人惊讶的是,当共享可变性消失的时候,所有那些令人纠结的问题也都随之消失了. 事实证明,在相同数据集上起多个线程互相冲突地执行是行不

讨喜的隔离可变性(一)用角色实现隔离可变性

Java将OOP变成了可变性驱动(mutability-driven)的开发模式[1],而函数式编程则着重强调不可变性,而这两种极端的方式其实都是有问题的.如果每样事物都是可变的,那么我们就需要妥善处理可见性和竞争条件.而在一个真实的应用程序中,也并非所有事物都是不可变的.即使是纯函数式语言也提供了代码限制区,在该区域内允许出现带副作用的逻辑以及按顺序执行这些逻辑的方法.但无论我们倾向于哪种编程模型,避免共享可变性都是毋庸置疑的. 共享可变性--并发问题的根源所在--是指多个线程可以同时更改相同

讨喜的隔离可变性(六)多角色协作

在使用基于角色的编程模型时,只有当多个角色互相协作.同心协力解决问题时,我们才能真正从中获益并感受到其中的乐趣.为了更好地利用并发的威力,我们通常需要把问题拆分成若干个子问题.不同的角色可以负责不同的子问题,而我们则需要对角色之间的通信进行协调.下面我们将通过重写计算目录大小的例子来学习如何在进行多角色协作. 在4.2节中,我们写了一个计算给定目录下所有文件大小的程序.在那个例子中,我们启动了100个线程,每个线程都负责扫描不同的子目录,并在最后异步地将所有计算结果累加在一起.而本节中我们将看到

讨喜的隔离可变性(五)同时使用多个角色

声明:本文是<Java虚拟机并发编程>的第五章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文. 在使用基于角色的编程模型时,只有当多个角色互相协作.同心协力解决问题时,我们才能真正从中获益并感受到其中的乐趣.为了更好地利用并发的威力,我们通常需要把问题拆分成若干个子问题.不同的角色可以负责不同的子问题,而我们则需要对角色之间的通信进行协调.下面我们将通过重写计算目录大小的例子来学习如何在进行多角色协作. 在4.2节中,我们写了一个计算给定目录下所有文件大小的程序.在那个例子中

讨喜的隔离可变性(七)使用类型化角色

到目前为止我们所接触过的角色都是可以接收消息的,而消息的类型也是五花八门,如String.元组.case类/自定义消息等.然而发送消息的行为在感觉上与我们日常编程工作中所使用的常规函数调用还是有很大区别的,为了弥合二者之间的鸿沟,类型化角色(Typed Actor)就应运而生了.这种类型的角色可以将发送消息的动作在形式上伪装成常规的函数调用,而将消息传输动作隐藏在后台执行.我们可以将类型化角色想像成为一个活动的对象,该对象运行在一个属于自己的轻量消息驱动的线程里面,并且还带有一个用于将正常的函数

讨喜的隔离可变性(八)类型化角色和Murmurs

使用了类型化角色的EnergySource使我们能够以调用函数的形式来掩盖后台顺序处理异步消息的过程,在实现了线程安全的同时又可以免去显式同步的困扰.虽然创建类型化角色并不困难,但此时我们的EnergySource却还是一个丢失了关键特性的半成品--即还没有可以周期性自动补充电量的能力. 在上一章我们所实现的版本中,由于整个动作都是在后台完成,所以电量补充的动作是不需要任何用户介入的.只要我们启动了电源,就会有一个专门的timer负责每秒钟为电源增加一格电量. 然而在使用了类型化角色的版本中,实

讨喜的隔离可变性(九)混合使用角色和STM

角色可以帮助我们对可变状态进行很好地隔离.尤其是当问题能够被拆分成可以独立运行的多个并发任务.并且并发任务彼此之间都是通过消息进行异步通信时,角色的表现更佳.但是,角色并未提供对跨任务的一致性进行管理的方法.所以如果我们希望两个或多个角色的动作要么全部成功.要么全部失败,则角色就无法独立实现,而此时我们就需要通过引入STM来与角色配合完成此类功能.在本节中,我假定你已经阅读过第6章.以及本章中有关角色和类型化角色的相关内容. 我们在4.6节和6.9节中曾经实现过一个用于在两个Account之间转

讨喜的隔离可变性(十一)调和类型化角色

正如我们在8.7节中所看到的那样,类型化角色是吸取了面向对象的程序设计和基于角色的程序设计二者的精华所孕育出来的新编程模型.该编程模型集所有我们耳熟能详的方法于一身,既可以方便地使用函数调用,又能享受角色所带来的好处.所以,在一个OO应用程序中,相比起普通的角色,我们可能会更倾向于使用类型化的角色.然而与普通角色相类似的是,类型化角色也是各自独立运行且不提供彼此间事务协调功能的,下面我们将会使用调和类型化角色(coordinating typed actor)来解决这个问题. Akka帮助我们简