2012-11-19 22 views
0

我目前正在使用Scala使用的日志记录机制,但遇到了一个意外问题,导致无法正常工作。为了测试功能,我正在设置一个简单的消息传递环。在环内,每个节点都是Scala Actor的扩展,并且知道它是直接的邻居(上一个/下一个)。意外更改为Scala 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的绳索,希望我没有忽略一些简单的错误。

回答

3

我认为这个问题是这样的:

角色模型本质上是一个异步模型,这意味着有时淡漠发送时间演员处理消息。

您正在向您的每个演员发送对大小为2的数组的引用,该数组会根据您的迭代状态不断更改其内容。但是,演员在呼叫nodes(i) ! spliceArr后不会处理初始化消息。所以可能发生的是迭代完成,并且只有在这之后,演员才会被安排处理消息。麻烦的是,它们全都看到spliceArr的实例,就像for循环完成时一样。

所以简单的解决方法是不发送数组,但一对:

nodes(i) ! spliceArr 

变得

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

和还应该在循环之前修改对应的线。这个改变也应该在actor的代码中执行 - 使用元组而不是数组来处理这种东西。

如果我的猜测是正确的,那么核心问题是你正在使用可变数据结构(在你的案例中的数组),这是在各种实体(演员,在你的例子)之间共享。这会导致产生的问题所以除非你真的工作的应用程序有状态的数据结构的特定需要,你应该总是在不变性打赌。

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

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

可变消息是不好的,因为它们可以导致参与者共享状态 - 包含在消息中的行为者并发执行环境中的状态可能会导致很难发现问题。

下面的代码将如何看起来像上面描述的修补程序:

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) 
       } 
      } 
     } 
    } 
} 

我也对算法获得更好的可读性做了一些改变,我希望你不要介意。

+0

感谢您的详细解释。我将使用这种方法,看看它是否解决了不一致的问题。它看起来好像是一个并发问题,通常结果很难解决,但你的解释肯定给我提供了我以前没有的方向。 –

+0

请考虑制作除scala.actors.Actor上的'act' private之外的所有内容,因为使方法公开邀请其他人(或其他代码)破坏actor模型封装。 –

+0

@RolandKuhn - 感谢您的观察!多么令人尴尬,所有这些关于封装的讨论,我都把我的可变域公开了:) –