为什么我应该使用 context.become() 来存储 Actor 中的内部状态?
Why I should use context.become() to store internal state in Actor?
我读过 scala-best-practices 并且有一个关于“5.2. 应该只用 context.become 改变 actors 中的状态”的问题。我不明白为什么使用 var
存储内部状态如此糟糕。如果演员按顺序执行所有消息,我就看不到任何问题的根源。我想念什么?
考虑您引用的 article 中的第一个示例:
class MyActor extends Actor {
val isInSet = mutable.Set.empty[String]
def receive = {
case Add(key) =>
isInSet += key
case Contains(key) =>
sender() ! isInSet(key)
}
}
此示例本身没有任何错误,因此您没有遗漏任何重要的理解。如您所知,参与者按顺序处理其邮箱中的消息,因此 在内部变量中表示其状态并改变此状态是安全的,只要演员没有暴露那个状态1.
become
通常用于动态切换 actor 的行为(例如,更改 actor 处理的消息类型)and/or 其状态。在文章的第二个例子中,actor 的状态作为参数编码在它的行为中。这是第一个示例的优雅替代方案,但在这种简单的情况下,这是一个偏好问题。
然而,become
可以真正发挥作用的一个场景是具有许多状态转换的 actor。在不使用 become
的情况下,actor 在其 receive
块中的逻辑可能会变得难以控制,或者变成一堆 if-else
语句。作为使用 become
模拟状态转换的示例,请查看此 sample project that models the "Dining Philosophers" 问题。
1一个潜在的问题是,虽然 isInSet
是一个 val
,但它是一个可变的 Set
,所以如果actor 将这个状态暴露给 actor 本身之外的东西(在示例中它没有这样做)。例如,如果 actor 在消息中将此 Set
发送给另一个 actor,则外部 actor 可以更改此状态,从而导致意外行为或竞争条件。可以通过将 val
更改为 var
并将可变 Set 更改为不可变 Set
.
来缓解此问题
我认为在 actor 中使用 var
s 不一定有什么问题,正是出于您提到的原因(不过请记住,这仅适用于在 receive(...)
,即,如果您启动一个线程,或使用 Future
,即使它来自 receive
,该代码也不再按顺序执行)。
不过,我个人更喜欢使用context.become(...)
来控制状态,主要是因为它清楚地向我展示了actor中可以改变的状态(var
可以分散在各处)。
我也更喜欢将其限制为每处理一条消息对 context.become(...)
调用 0 次或 1 次,因此很清楚此状态转换发生的位置。
也就是说,您可以通过使用在一个地方定义所有 var
的约定来获得相同的好处,并确保在一个地方重新分配它们(说到最后)在您的消息处理中。
我读过 scala-best-practices 并且有一个关于“5.2. 应该只用 context.become 改变 actors 中的状态”的问题。我不明白为什么使用 var
存储内部状态如此糟糕。如果演员按顺序执行所有消息,我就看不到任何问题的根源。我想念什么?
考虑您引用的 article 中的第一个示例:
class MyActor extends Actor {
val isInSet = mutable.Set.empty[String]
def receive = {
case Add(key) =>
isInSet += key
case Contains(key) =>
sender() ! isInSet(key)
}
}
此示例本身没有任何错误,因此您没有遗漏任何重要的理解。如您所知,参与者按顺序处理其邮箱中的消息,因此 在内部变量中表示其状态并改变此状态是安全的,只要演员没有暴露那个状态1.
become
通常用于动态切换 actor 的行为(例如,更改 actor 处理的消息类型)and/or 其状态。在文章的第二个例子中,actor 的状态作为参数编码在它的行为中。这是第一个示例的优雅替代方案,但在这种简单的情况下,这是一个偏好问题。
然而,become
可以真正发挥作用的一个场景是具有许多状态转换的 actor。在不使用 become
的情况下,actor 在其 receive
块中的逻辑可能会变得难以控制,或者变成一堆 if-else
语句。作为使用 become
模拟状态转换的示例,请查看此 sample project that models the "Dining Philosophers" 问题。
1一个潜在的问题是,虽然 isInSet
是一个 val
,但它是一个可变的 Set
,所以如果actor 将这个状态暴露给 actor 本身之外的东西(在示例中它没有这样做)。例如,如果 actor 在消息中将此 Set
发送给另一个 actor,则外部 actor 可以更改此状态,从而导致意外行为或竞争条件。可以通过将 val
更改为 var
并将可变 Set 更改为不可变 Set
.
我认为在 actor 中使用 var
s 不一定有什么问题,正是出于您提到的原因(不过请记住,这仅适用于在 receive(...)
,即,如果您启动一个线程,或使用 Future
,即使它来自 receive
,该代码也不再按顺序执行)。
不过,我个人更喜欢使用context.become(...)
来控制状态,主要是因为它清楚地向我展示了actor中可以改变的状态(var
可以分散在各处)。
我也更喜欢将其限制为每处理一条消息对 context.become(...)
调用 0 次或 1 次,因此很清楚此状态转换发生的位置。
也就是说,您可以通过使用在一个地方定义所有 var
的约定来获得相同的好处,并确保在一个地方重新分配它们(说到最后)在您的消息处理中。