意外更改Scala Actor的局部变量 [英] Unintended change to local variable of a Scala Actor

查看:116
本文介绍了意外更改Scala Actor的局部变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am currently working on a logging mechanism to be used with Scala, but I have run into an unexpected issue that has prevented me from actually working on it. In order to test functionality, I am looking to set-up a simple message passing ring. Within the ring, each node is an extension of a Scala Actor and is aware of it's immediate neighbors (previous/next).

按以下步骤完成环的构造,并从控制器参与者中传递参数节点":

The ring construction is done as follows, with the parameter "nodes" being passed from a controller 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的数组,其设置如下:

This appears to function as I would like it to, with each node receiving the correct pair of neighbors. In the node class there is an array of size 2 which is set as follows:

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(从节点0开始)传递的消息时,我发现每个节点现在在它的ringNeighbors数组中都具有相同的值.这些值与ringBuilder(即节点8的邻居)函数中循环的最终迭代一致.没有发生任何其他消息传递,因此我不了解如何针对节点数组(因此是环)中的每个实例修改这些值.

Everything is fine through is point, however, when I introduce a message to be passed around the ring (from node 0), I have discovered that every node now has the same values in it's ringNeighbors array. These values are consistent with the final iteration of the loop in the ringBuilder (i.e. node 8's neighbors) function. There is no additional message passing which occurs so I do not understand how these values were modified for each instance in the nodes array and thus the ring.

我仍在学习Scala的绳索,希望我不要误以为是简单的事情.

I am still learning the ropes of Scala and hope I did not overlook something simple by mistake.

推荐答案

我认为问题是这样的:

Actor模型本质上是一个异步模型,这意味着Actor在与发送时间无关的时间处理消息.

The Actor model is essentially an asynchronous model, meaning that actors process messages at times indifferent to the sending times.

您正在向每个参与者发送对大小为2的数组的引用,该数组会根据迭代状态不断更改其内容.但是,参与者在调用nodes(i) ! spliceArr之后不会立即处理初始化消息.因此,可能发生的事情是迭代完成,并且只有在安排参与者处理消息之后才进行迭代.麻烦的是,当for循环完成时,所有人都看到了spliceArr的实例.

You are sending to each of your actor a reference to a size 2 array, which keeps changing its content depending on the state of your iteration. However, actors will not process the initialization message right after the call nodes(i) ! spliceArr. So what probably happens is that the iteration finishes and only after that the actors are scheduled to process the messages. The trouble is, all of them see the instance of spliceArr as it is when the for loop has finished.

因此,简单的解决方案是不发送数组,而是发送一对:

So the simple solution is to not send an array but a pair:

nodes(i) ! spliceArr

成为

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

,还应该在循环之前修改相应的行.这种更改也应该在参与者的代码中执行-对于此类内容,请使用元组而不是数组.

and you should also modify the corresponding lines before the loop. This change should be performed also in the code of the actors - use tuples instead of arrays for this kind of stuff.

如果我的猜测是正确的,那么核心问题是您正在使用可变的数据结构(在您的情况下为数组),这些数据结构在各个实体(在您的示例中为参与者)之间共享.这总是会带来问题,因此,除非您正在处理的应用程序确实对有状态数据结构有特殊需求,否则您应该始终将赌注押在不变性上.

If my guess was right, then the core problem is that you're using mutable data structures (the array in your case) which are shared amongst various entities (the actors, in your example). This always leads to problems so unless the application you are working on really has a particular need for stateful data structures you should always bet on immutability.

现在,在参与者系统的特定情况下,参与者之间交换的消息具有更大的不变性需求.参与者应该是封闭的数据结构,并且它们的状态不应从外部访问.另外,在参与者系统中,不应有全局状态.

Now, in the particular case of actor systems the messages exchanged between actors have an even greater need to be immutable. Actors are supposed to be enclosed data structures and their state should not be accessible from the exterior. Also, in an actor system there should be no global state.

不幸的是,与其他实现参与者系统的语言(例如Erlang)不同,Scala无法强制执行此行为.因此,确保这种情况发生是开发人员的工作.

Unfortunately, unlike other languages that implement actor systems such as Erlang, Scala cannot enforce this behavior. Therefore it is the job of the developer to ensure this happens.

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

Mutable messages are bad since they can cause actors to share state - the state contained in the message which in the context of the actor's concurrent execution will probably lead to hard to spot issues.

这是上面描述的修复代码的样子:

Here's how the code would look like with the fixes described above:

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

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

I have also made some changes for greater readability of the algorithm, I hope you don't mind.

这篇关于意外更改Scala Actor的局部变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆