0

私は現在、Scalaで使用するロギングメカニズムに取り組んでいますが、予期しない問題が発生し、実際に作業できなくなりました。機能をテストするために、簡単なメッセージパッシングリングを設定することを検討しています。リング内では、各ノードはScalaアクターの拡張であり、そのすぐ隣(前/次)を認識しています。

リングの構築は次のように行われ、パラメーター「ノード」はコントローラーアクターから渡されます。

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のロープを学んでおり、誤って単純なものを見落としていないことを願っています。

4

1 に答える 1

3

問題は次のとおりだと思います。

アクターモデルは本質的に非同期モデルです。つまり、アクターは送信時間とは無関係にメッセージを処理します。

サイズ2の配列への参照を各アクターに送信します。この配列は、反復の状態に応じてコンテンツを変更し続けます。ただし、アクターは呼び出しの直後に初期化メッセージを処理しませんnodes(i) ! spliceArr。したがって、おそらく何が起こるかというと、反復が終了し、その後でのみ、アクターがメッセージを処理するようにスケジュールされます。spliceArr問題は、 forループが終了したときにすべてのインスタンスがそのまま表示されることです。

したがって、簡単な解決策は、配列ではなくペアを送信することです。

nodes(i) ! spliceArr

になります

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

また、ループの前に対応する行を変更する必要があります。この変更は、アクターのコードでも実行する必要があります。この種のものには、配列の代わりにタプルを使用してください。

私の推測が正しければ、主要な問題は、さまざまなエンティティ(この例ではアクター)間で共有される可変データ構造(この場合は配列)を使用していることです。これは常に問題につながるため、作業中のアプリケーションにステートフルなデータ構造が特に必要でない限り、常に不変性に賭ける必要があります。

さて、アクターシステムの特定のケースでは、アクター間で交換されるメッセージはさらに不変である必要があります。アクターは囲まれたデータ構造であると想定されており、それらの状態に外部からアクセスできないようにする必要があります。また、アクターシステムでは、グローバルな状態があってはなりません。

残念ながら、Erlangなどのアクターシステムを実装する他の言語とは異なり、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)
                }
            }
        }
    }
}

また、アルゴリズムを読みやすくするためにいくつかの変更を加えました。気にしないでください。

于 2012-11-19T23:46:14.540 に答える