意外更改Scala Actor的局部变量

时间:2012-11-19 21:58:18

标签: scala actor-model

我目前正在研究与Scala一起使用的日志记录机制,但我遇到了一个意外的问题,导致我无法实际使用它。为了测试功能,我希望设置一个简单的消息传递环。在环中,每个节点都是Scala Actor的扩展,并且知道它是直接邻居(上一个/下一个)。

环结构如下所示,参数“nodes”从控制器actor传递:

def buildRing(nodes:Array[Actor]){
  var spliceArr:Array[Actor] = new Array[Actor](2)
  spliceArr(0) = nodes(nodes.length-1)
  spliceArr(1) = nodes(1)
  nodes(0) !  spliceArr
  Thread.sleep(100)
  spliceArr(0) = nodes(nodes.length-2)
  spliceArr(1) = nodes(0)
  nodes(nodes.length-1) ! spliceArr
  Thread.sleep(100)
  for(i <-1 to numNodes-2){
      spliceArr(0) = nodes(i-1)
      spliceArr(1) = nodes(i+1)
      nodes(i) ! spliceArr
      Thread.sleep(100)
  }
}

这似乎按照我的意愿运行,每个节点都接收到正确的邻居对。在节点类中,有一个大小为2的数组,其设置如下:

class node(locLogger:logger,nid:Int,boss:Actor) extends Actor{
  val ringNeighbors:Array[Actor] = new Array[Actor](2)
  def act{
    locLogger.start
    loop{
        receive{
            case n:Array[Actor] =>
              ringNeighbors(0) = n(0)
              ringNeighbors(1) = n(1)

一切都很好,但是,当我介绍一个要在环上传递的消息(从节点0)时,我发现每个节点现在在它的ringNeighbors数组中具有相同的值。这些值与ringBuilder(即节点8的邻居)函数中的循环的最终迭代一致。没有其他消息传递发生,所以我不明白如何为节点数组中的每个实例修改这些值,从而修改环。

我仍在学习Scala的绳索,并希望我不会忽略一些简单的错误。

1 个答案:

答案 0 :(得分:3)

我认为问题如下:

Actor模型本质上是一个异步模型,这意味着actor会在不同于发送时间的情况下处理消息。

您正在向每个actor发送一个对size 2数组的引用,该数组会根据迭代的状态不断更改其内容。但是,演员不会在调用nodes(i) ! spliceArr之后立即处理初始化消息。所以可能发生的事情是迭代结束,并且只有在此之后才调度actor来处理消息。麻烦的是,所有人都看到了spliceArr的实例,就像for循环结束一样。

所以简单的解决方案是不发送数组而是一对:

nodes(i) ! spliceArr

变为

nodes(i) ! (nodes(i-1), nodes(i+1))

并且您还应该在循环之前修改相应的行。这个更改也应该在actor的代码中执行 - 使用元组而不是数组来表示这种东西。

如果我的猜测是正确的,那么核心问题是你正在使用可变数据结构(在你的情况下是数组),它们在各种实体(在你的例子中为actor)之间共享。这总是会导致问题,所以除非您正在处理的应用程序确实特别需要有状态数据结构,否则您应该始终坚持不变性。

现在,在actor系统的特定情况下,actor之间交换的消息更需要不可变。演员应该是封闭的数据结构,不应该从外部访问它们的状态。此外,在演员系统中,应该没有全局状态。

不幸的是,与实现诸如Erlang之类的actor系统的其他语言不同,Scala无法强制执行此行为。因此,开发人员的工作就是确保这种情况发生。

可变消息很糟糕,因为它们可能导致actor共享状态 - 消息中包含的状态在actor的并发执行的上下文中可能导致很难发现问题。

以下是上述修复程序的代码:

def buildRing(nodes: Array[Actor]) {
    nodes.zipWithIndex.foreach {
        case (actor, index) => actor ! (previous(nodes, index), next(nodes, index))
    }
}

//Gets the next actor from the ring for the specified index.
def next(nodes: Array[Actor], index: Int): Actor = {
    val k = (index + 1) % nodes.length
    nodes(k)
}

//Gets the previous actor
def previous(nodes: Array[Actor], index: Int): Actor = {
    val k = if (index == 0) nodes.length - 1 else index - 1
    nodes(k)
}

class Node(locLogger:logger, nid:Int, boss:Actor) extends Actor {
    private var leftNeighbour: Option[Actor] = None //avoid using null in favor of Option
    private var rightNeighbour: Option[Actor] = None
    def act {
        locLogger.start
        loop {
            receive {
                case (left, right) => {
                    leftNeighbour = Some(left)
                    rightNeighbour = Some(right)
                }
            }
        }
    }
}

我也做了一些更改以提高算法的可读性,我希望你不介意。