我已阅读scala-best-practices并对" 5.2提出疑问。应该只使用context.become"在actor中改变状态。我不明白为什么使用var
存储内部状态是如此糟糕。如果actor按顺序执行所有消息,我就无法看到任何问题来源。我错过了什么?
答案 0 :(得分:6)
考虑您引用的article中的第一个示例:
class MyActor extends Actor {
val isInSet = mutable.Set.empty[String]
def receive = {
case Add(key) =>
isInSet += key
case Contains(key) =>
sender() ! isInSet(key)
}
}
这个例子没有任何本质上的错误,所以没有一些重要的理解,你错过了。如你所知,一个actor按顺序处理其邮箱中的消息,因此 可以安全地在一个内部变量中表示它的状态,并且只要该actor没有&#39>就会改变这个状态。 ; t暴露该状态 1 。
become
通常用于动态切换演员的行为(例如,改变演员处理的消息类型)和/或其状态。在本文的第二个例子中,演员的状态在其行为中被编码为参数。这是第一个例子的优雅替代方案,但在这个简单的例子中,它是一个偏好的问题。
然而,become
能够真正发挥作用的一种情况是具有许多状态转换的演员。如果不使用become
,其receive
块中的参与者逻辑可能会无法管理地变大或变成if-else
语句的丛林。作为使用become
建模状态转换的示例,请查看为sample project问题建模的"Dining Philosophers"。
1 一个潜在的问题是虽然isInSet
是一个val
,但它是一个可变的Set
,所以如果演员可能会发生奇怪的事情将此状态暴露给actor本身之外的东西(在示例中它没有做到)。例如,如果actor在消息中将此Set
发送给另一个actor,则外部actor可以更改此状态,从而导致意外行为或竞争条件。可以通过将val
更改为var
,将可变设置更改为不可变Set
来缓解此问题。
答案 1 :(得分:2)
我认为在演员中使用var
并不一定有任何问题,正是因为你提到的原因(请记住,这只适用于在{{1 ,即,如果您启动一个线程,或使用receive(...)
,即使它来自Future
,该代码也不再按顺序执行。
但是,我个人更喜欢使用receive
来控制状态,主要是因为它清楚地向我显示了演员中可以改变的状态(context.become(...)
s可以分散在各处。)
我还希望每处理一条消息将其限制为0或1次调用var
,因此很清楚这种状态转换发生的位置。
也就是说,您可以通过使用约定来获得相同的好处,您可以在一个地方定义所有context.become(...)
,并确保在一个地方重新分配它们(比如说到最后)消息处理。