基于角色的并发模型降低了隔离可变性编程的难度,但该模型在适用场景上还是存在一些限制。
由于角色是通过消息来进行彼此间通信的,所以在那些没有强制不可变性的语言中,我们就必须人工来保证消息都是不可变的。传递可变消息将导致线程安全问题并最终使整个应用陷入共享可变性的险境当中,所以当手头的辅助工具还没有发展到可以帮助我们自动查验消息的不可变性之前,保证消息不可变性的重担暂时还是得由我们程序员来肩负。
角色都是各自异步运行的,彼此之前可以通过传递消息来进行协作。但某些角色的意外失败有可能导致其他角色饿死——一个或多个角色可能会一直等待某些关键的协作消息,而这些消息可能由于本应发出该消息的角色失败而永远无法抵达。因此,我们需要在编码时更加谨慎,在角色中加入异常情况的处理逻辑,并将错误消息传播给那些等待响应的角色。
当两个或多个角色互相等待对方发来的消息时,则所有角色都将陷入死锁。我们必须在设计层面谨小慎微,以确保角色在协作过程中不会陷入死锁状态。与此同时,我们应该使用超时来保证程序不会由于协作环节中个别角色失败无法响应而导致其他角色无限期的等待。
角色一次只能处理一个消息请求,所以无论是动作消息还是请求某个响应或状态的消息,在角色内部都是顺序处理的。对于那些只读任务而言,这种做法可能会降低程序整体的并发度。所以我们最好还是用粗粒度的消息来设计应用程序。此外,我们还可以通过设计单向的“发送并遗忘”类型的消息代替双向消息来减少角色之间的相互等待。
并非所有的应用程序都适合用基于角色的模型实现。只有在待解决的任务可以被拆分成多个彼此相互独立的子任务并且子任务之间只有少量交互的情况下,使用角色才是合适的。但如果子任务之间需要频繁交互或者子任务们需要通过形成一个裁决集(quorum)的形式来进行协作,则使用基于角色的模型就是不合适的。而对于这类问题,我们可以尝试将基于角色的模型与其他并发模型混搭或干脆考虑重新设计。
小结
角色是单线程的,彼此间通过传递消息来进行交互。通过本章的学习,我们了解到角色有如下特性:
- 降低了隔离可变性的使用门槛
- 从根本上消除了同步问题
- 提供了高效的单向消息,但同时也提供了不怎么高效的发送并等待功能
- 可扩展性非常强;同时由于角色都是单线程的,所以我们可以很方便地用线程池来对其进行管理
- 允许我们发送消息,但同时也通过接口的方式支持类型化版本(在Akka中)。
- 允许我们通过事务来完成多角色之间的协作
虽然角色为我们提供了一种强大的编程模型,但该模型在某些方面仍有一些限制。例如,如果我们在使用角色进行设计的时候没有考虑周到,则程序可能存在角色饿死或死锁的潜在风险。此外,我们还需要保证消息的不可变性,这一点在没有语言层面的支持的环境中尤为重要。
下一章我们将学习如何在各种JVM支持的语言中使用角色。