在下面的代码中,尽管我是一个对象,但我正在使用AKKA actor MonitorActor。尽管它似乎运行良好,但在生产代码中我从未见过这种模式。
以下代码是否由于将对象用作Actor而导致并发问题?
这里有没有与AKKA演员相关的“陷阱”?
case class SomeEvent(member: String)
class Example(eventBus: EventBus)(implicit actorSystem: ActorSystem) {
val members: AtomicReference[Set[String]] = new AtomicReference(Set())
actorSystem.actorOf(Props(MonitorActor))
private object MonitorActor extends Actor {
eventBus.subscribe(classOf[SomeEvent])
var isEnough = false
override def receive: Receive = {
case SomeEvent(member: String) =>
val newMembers = members.updateAndGet(_ + member)
if (newMembers.size >= 10) {
isEnough = true
}
}
}
}
答案 0 :(得分:1)
此“模式”引起的一个直接问题是:如果将Actor
两次添加到actorSystem
会发生什么:
actorSystem.actorOf(Props(MonitorActor))
actorSystem.actorOf(Props(MonitorActor))
这不是一个小问题。在大型代码库中,可以包含多个Actor的文件/包,因此,如果只是偶然,可能会出现上述情况。
充其量,每个SomeEvent
被完全相同的逻辑处理两次。在最坏的情况下,您会遇到isEnough
令人讨厌的比赛条件。因此,让我们假设最好的情况。
即使在最佳情况下,每个SomeEvent也将由完全相同的逻辑进行处理。在问题的示例中这还不错,因为members
是Set
。但是,如果它是List
,您将开始两次插入同一事件。
另一个问题是必须保护自己免受涉及members
的比赛条件的影响。 members
成为AtomicReference
的一个很好的理由是要解决两个“独立”参与者试图同时访问members
的情况。但这违背了Actor模型的全部目的。来自original 1973 formalism(重点是我):
关于控制结构,体系结构是通用的,并且确实 不需要或不需要goto,interrupt或信号量原语。
在akka documentation's introduction(强调我的名字)中可以找到类似的描述:
Actor模型为写作提供了更高层次的抽象 并发和分布式系统。 减轻开发人员的负担 不得不处理显式锁定和线程管理, 更容易编写正确的并发和并行系统。
因此,我们已经有效地破坏了Actor模型框架,而我们所要做的只是不必调用构造函数。将问题的示例代码与“ preferable”实现进行对比:
class MonitorActor() extends Actor {
val members: Set[String] = Set.empty[String]
eventBus.subscribe(classOf[SomeEvent])
var isEnough = false
override def receive: Receive = {
case SomeEvent(member: String) => {
members add member
isEnough = members.size >= 10
}
}
}
现在,开发人员不必担心信号量,竞争条件,线程争用... ...从串行角度可以理解Actor中的所有逻辑和功能。