2012-11-19 2 views
0

현재 스칼라와 함께 사용되는 로깅 메커니즘에 대해 작업하고 있습니다 만, 실제로 작업하지 못하게하는 예기치 않은 문제가 발생했습니다. 기능을 테스트하기 위해 간단한 메시지 전달 링을 설정하려고합니다. 링 내에서 각 노드는 스칼라 액터의 확장이며 직계 이웃 (이전/다음)임을 인식합니다.Scala 액터의 로컬 변수로의 의도하지 않은 변경

링 구성은 제어기 액터로부터 전달 된 파라미터와, "노드"는 다음과 같이 수행된다 :

이 I가하고자하는대로 정확한 쌍을 수신하는 각 노드와 함께 작동하도록 나타나는
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) 

모두가 I 메시지를 소개 할 때 시점 그러나, (노드 0)에서 링 주위 전달됨을 통해 괜찮은 모든 노드가 이제 ringNeighbors 배열에 동일한 값을 가짐을 발견했습니다. 이 값은 ringBuilder (즉 노드 8의 이웃) 함수에서 루프의 최종 반복과 일치합니다. 추가적인 메시지 전달이 발생하지 않기 때문에 노드 배열과 링에서 각 인스턴스에 대해 이러한 값이 어떻게 수정되었는지 이해할 수 없습니다.

아직 스칼라 로프를 배우고 있으며 실수로 간단한 것을 간과하지 않기를 바랍니다.

답변

3

나는이 문제는 다음과 같은 생각 :

액터 모델은 의미 기본적으로 비동기 모델입니다 무관심 보내는 시간에 시간에 배우 프로세스 메시지.

각 배우에게 크기 2 배열에 대한 참조를 보냅니다.이 배열은 반복 상태에 따라 내용을 계속 변경합니다. 그러나 액터는 nodes(i) ! spliceArr 콜 바로 다음에 초기화 메시지를 처리하지 않습니다. 따라서 반복이 완료되고 그 후에 만 ​​배우가 메시지를 처리하도록 예약 된 것입니다. 문제는 for 루프가 끝날 때 모두가 spliceArr의 인스턴스를 볼 수 있다는 것입니다.

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

는 또한 알고리즘의 큰 가독성을 위해 일부 변경 한, 당신이 생각하지 않았 으면.

+0

자세한 설명을 가져 주셔서 감사합니다. 이 방법을 사용하여 불일치 문제를 해결하는지 확인합니다. 그것은 분명히 동시성 문제 인 것 같아서 해결하기가 상당히 불편한 것으로 판명되었지만 당신의 설명은 내가 전에 없었던 방향을 분명히 제공합니다. –

+0

public 메서드를 public으로 만들면 다른 사람 (또는 다른 코드)이 액터 모델 캡슐화를 중단하도록 초대하기 때문에 scala.actors.Actor에서'act'를 제외한 모든 것을 고려하십시오. –

+0

@RolandKuhn - 관측에 감사드립니다! 얼마나 당혹 스럽습니까, 캡슐화에 관한이 모든 이야기와 나는 변경할 수있는 필드를 공개했습니다 :) –

관련 문제