【发布时间】:2016-12-19 15:07:29
【问题描述】:
我目前在我的 Scala 代码中使用 akka Actor 系统。我有一个父演员和一个儿童演员。我想要做的是在父演员处启动一个列表并让子演员:
i)生成新元素并将它们发送给父actor,父actor会将它们附加到列表中。 ii) 随时访问列表元素。
我怎样才能实现第二(ii)部分?
【问题讨论】:
我目前在我的 Scala 代码中使用 akka Actor 系统。我有一个父演员和一个儿童演员。我想要做的是在父演员处启动一个列表并让子演员:
i)生成新元素并将它们发送给父actor,父actor会将它们附加到列表中。 ii) 随时访问列表元素。
我怎样才能实现第二(ii)部分?
【问题讨论】:
您必须使用消息传递。在两个参与者之间共享任何类型的可变状态都违反了 Akka 提供的所有保证。
或者,您可以在您的子actor和父actor之间共享一个Agent,这将使列表的更新线性化,同时允许同步读取。
【讨论】:
您可以选择父母可以回复的特定消息,以披露其内部状态。请参阅下面的示例:
import scala.collection.immutable.Queue
class Parent extends Actor {
import Parent._
// TODO: start children here
override def receive: Receive = receiveElements(Queue.empty[Element])
def receiveElements(acc: Queue[Element]): Receive = {
case e@Element => context.become(receiveElements(acc :+ e))
case GetElements => sender ! acc
}
}
object Parent {
case class Element(s: String)
case object GetElements
}
请注意,通过将 var queue 保留在 Parent 内部而不是使用 context.become+recursion 可以实现相同的行为。
此外,不设计刷新队列的方法可能会导致 OOME。
【讨论】:
好吧,我设法找到了解决方案。
如果您在父 Actor 内创建一个变量并将其作为构造参数传递给子 Actor ,则如果您不替换它,则此变量中的每个更改都是可见的。我会在一个错误的例子之后用一个正确的例子来解释。
class ParentActor(/*constructors here*/) extends Actor{
val list =ListBuffer[Int](1,2)
child = new ChildActor(list //+other constructors)
list=ListBuffer[Int](1,2,3) // **wrong** child will see list=List(1,2)
list+=3 // **right** child will see list=List(1,2,3) as wanted
}
class ChildActor(list : ListBuffer[Int] /*++ otherconstructors here*/) extends Actor{
list=ListBuffer[Int](1,2,3,4) // **wrong** parent will see list=List(1,2,3)
list+=4 // **right** child will see list=List(1,2,3,4) as wanted
}
【讨论】:
ListBuffer 不是线程安全的集合。其次,即使选择线程安全的集合也会违反位置透明性——因为序列化是不可能的。在这里进一步阅读manuel.bernhardt.io/2016/08/02/…