我目前正在研究与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的绳索,并希望我不会忽略一些简单的错误。
答案 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)
}
}
}
}
}
我也做了一些更改以提高算法的可读性,我希望你不介意。