2017-03-15 1 views
2

"pony"라는 프로그래밍 언어가 오늘 발견되었습니다 ... 그걸 가지고 놀기 시작했습니다.데이터 경쟁? 아니면 뭔가 잘못 됐어?

내 코드는 간단한 제작자 소비자 일을하기로되어 있습니다. 언어 설명서에 따라이 언어는 데이터 경합이 없음을 보장합니다.

여기에서 main은 생산자에게 10 개의 메시지를 보내고 소비자에게 10 개의 메시지를 보냅니다. 소비자는 카운터 상태 변수를 증가시킵니다. 그런 다음 main은 소비자에게 메시지를 보냅니다. 소비자에게 현재 값을 표시하기 위해 main에 메시지를 보냅니다. 모든 메시지가 순차적으로 나열되면 기대 값은 9 (또는 10)가됩니다. 결과는 인쇄되었지만 0입니다.

이것은 언어 연주를 한 시간 만에 완료 한 것이므로 물론 다른 것을 엉망으로 만들 수도 있습니다.

누가 내 실수를 설명 할 수 있습니까?

use "collections" 

actor Consumer 
    var _received : I32 
    new create() => 
     _received = 0 
    be tick() => 
     _received = _received + 1 
    be query(main : Main) => 
     main.status(_received) 

actor Producer 
    var _consumer : Consumer 
    new create(consumer' : Consumer) => 
     _consumer = consumer' 

    be produceOne() => 
     _consumer.tick() 

actor Main 
    var _env : Env 
    new create(env: Env) => 
     _env = env 
     let c : Consumer = Consumer.create() 
     let p = Producer.create(c) 
     for i in Range[I32](0,10) do 
      p.produceOne() 
     end 
     c.query(this) 

    be status(count : I32) => 
     // let fortyTwo : I32 = 42 
     // _env.out.print("fortytwo? " + (fortyTwo.string())) 
     _env.out.print("produced: " + (count.string())) 

Windows 10, 64 bit, btw에서 실행 중. 내가 찾은 가장 최신의 최고의 zip 파일 설치

0.10.0-1c33065 [출시] 다음으로 컴파일됩니다. llvm 3.9.0 -?

답변

4

포니가 막을 수있는 데이터 경주는 다른 사람이 메모리 위치에서 읽는 동안 누군가 메모리 위치에서 읽으면 메모리 수준에서 발생하는 것입니다. 이는 유형 시스템과 공유 된 가변 상태를 금지함으로써 방지됩니다.

그러나 결과는 Pony에서 보장하지 않는 메시지 순서에 따라 달라지면 프로그램에 "논리적"데이터 경쟁이있을 수 있습니다. 포니는 메시지의 인과 순서를 보장합니다. 즉, 보내거나받은 메시지가 메시지가 동일한 대상을 가지고있을 경우 보내거나받을 미래의 메시지의 원인이며, 그 원인이 효과 이전에 발생해야합니다. A이 두 개의 메시지가 여전히 임의의 순서로 수신 할 수 있습니다 (C에 메시지를 보내기 전에 B에 메시지를 보내기 때문에이 예에서

actor A 
    be ma(b: B, c: C) => 
    b.mb() 
    c.mc(b) 

actor B 
    be mb() => 
    None 

actor C 
    be mc(b: B) => 
    b.mb() 

B 항상 C에서 메시지 전에 A에서 메시지를 받게됩니다 그들은 같은 목적지가 없기 때문에). 즉 BC으로 전송 된 메시지는 BA으로 전송 된 메시지 이후에 전송되며 둘 다 동일한 대상을 가지므로 인과 관계가 있습니다.

프로그램의 인과 관계 순서를 살펴 보겠습니다. ->가되고, 우리는

  • Consumer.create -> Consumer.query
  • Consumer.create -> Consumer.tick (Producer.produceOne을 통해) (Consumer.query을 통해)

    • Main.create -> Main.status이 "의 원인은"
    • Producer.create -> Producer.produceOne

    당신이 볼 수 있듯이 , Consumer.queryConsumer.tick 사이에는 인과 관계가 없습니다.실제 구현의 의미에서 이것은 이 produceOne 메시지를 전송 한 다음 Producer이 수신 한 메시지를 실행하기 전에 query 메시지를 보내고 tick 메시지를 전송할 수 있음을 의미합니다. 하나의 스케줄러 스레드 (--ponythreads=1을 명령 행 인수로 사용)로 프로그램을 실행하면 create 끝까지 Main이 유일한 스케줄러를 독점하기 때문에 항상 produced: 0을 인쇄합니다. 여러 스케줄러 스레드를 사용하면 모든 스케줄러가 다른 액터를 실행 중이거나 Producer을 즉시 실행할 수 있기 때문에 0에서 10 사이의 값이 발생할 수 있습니다.

    요약하면 tickquery 동작은 특정 순서로 실행될 수 있습니다. 문제를 해결하려면 왕복 메시지를 추가하거나 동일한 액터에서 누적 및 인쇄를 수행하여 메시지 사이에 인과 관계를 도입해야합니다.

  • +0

    나는이 언어에 대한 다양한 프레젠테이션에서 내가들은 것들 중 일부를 잘못 해석 한 것 같습니다. 인과 관계 메시지 순서화 : 소비자 관점에서 볼 때 인과 관계 메시지 처리 순서는 Tick (10 회), Query (한 번)입니다. 소비자 행위자는 메시지를 순서대로 처리해야합니다. 각 액터가 (무제한) 메시지 큐를 가지고 메시지를 가져온다는 주장을 감안할 때, 소비자 액터의 스케줄링은 그 순서대로 이루어져야합니다. 어쩌면 문서가 실제로 메시지를 처리하는 방법에 대한 자세한 설명이 있어야합니다. – BitTickler

    0

    @Benoit Vey에게 감사드립니다.

    실제로 쿼리 실행과 생성자가 소비자에게 tick() 메시지를 보내는 사이에 명시 적이거나 암시적인 오류가없는 경우가 있습니다.

    그런 의미에서 부두교도 마법도 없습니다. 어떤 행위자 시스템도 행동 할 것으로 예상되는 것처럼 행동합니다.

    액터 내의 메시지는 순서대로 처리됩니다. 따라서 원하는 프로그램 동작을 얻으려면 생산자가 결국 쿼리를 트리거해야합니다 (produceOne 메시지를 처리 ​​한 후 순서대로). 여기

    , 즉이 수행 할 수있는 방법 :

    use "collections" 
    
    actor Consumer 
        var _received : I32 
        new create() => 
         _received = 0 
        be tick() => 
         _received = _received + 1 
        be query(main : Main) => 
         main.status(_received) 
    
    actor Producer 
        var _consumer : Consumer 
        new create(consumer' : Consumer) => 
         _consumer = consumer' 
    
        be produceOne() => 
         _consumer.tick() 
    
        be forward (main : Main) => 
         main.doQuery(_consumer) 
    
    actor Main 
        var _env : Env 
        new create(env: Env) => 
         _env = env 
         let c : Consumer = Consumer.create() 
         let p = Producer.create(c) 
         for i in Range[I32](0,10) do 
          p.produceOne() 
         end 
         //c.query(this) 
         p.forward(this) 
    
        be doQuery (target : Consumer) => 
         target.query(this) 
    
        be status(count : I32) => 
         // let fortyTwo : I32 = 42 
         // _env.out.print("fortytwo? " + (fortyTwo.string())) 
         _env.out.print("produced: " + (count.string())) 
    

    그냥 웃음 (비교), 나는 또한 F 번호에 같은 구현했습니다. 놀랍게도 조랑말은 소형이라는 범주에서 승리합니다. 포니 코드 39 줄, F # 줄 80 줄 네이티브 코드 생성과 함께 포니는 실제로 재미있는 언어 선택이됩니다.

    open FSharp.Control 
    
    type ConsumerMessage = 
        | Tick 
        | Query of MailboxProcessor<MainMessage> 
    
    and ProducerMessage = 
        | ProduceOne of MailboxProcessor<ConsumerMessage> 
        | Forward of (MailboxProcessor<MainMessage> * MainMessage) 
    
    and MainMessage = 
        | Status of int 
        | DoQuery of MailboxProcessor<ConsumerMessage> 
    
    let consumer = 
        new MailboxProcessor<ConsumerMessage> 
         (fun inbox -> 
          let rec loop count = 
           async { 
            let! m = inbox.Receive() 
            match m with 
            | Tick -> 
             return! loop (count+1) 
            | Query(target) -> 
             do target.Post(Status count) 
             return! loop count 
           }   
          loop 0 
         ) 
    
    let producer = 
        new MailboxProcessor<ProducerMessage> 
         (fun inbox -> 
          let rec loop() = 
           async { 
            let! m = inbox.Receive() 
            match m with 
            | ProduceOne(consumer') -> 
             consumer'.Post(Tick) 
             return! loop() 
            | Forward (target, msg) -> 
             target.Post(msg) 
             return! loop() 
           } 
          loop() 
         ) 
    
    let main = 
        new MailboxProcessor<MainMessage> 
         (fun inbox -> 
          let rec loop() = 
           async { 
            let! m = inbox.Receive() 
            match m with 
            | Status(count) -> 
             printfn "Status: %d" count 
             return! loop() 
            | DoQuery(target) -> 
             target.Post(Query inbox) 
             return! loop() 
           } 
          loop() 
         ) 
    
    let init() = 
        main.Start() 
        consumer.Start() 
        producer.Start() 
    
    let run() = 
        for _ in [1..10] do 
         producer.Post(ProduceOne consumer) 
        producer.Post(Forward(main,DoQuery consumer)) 
    
    let query() = 
        consumer.Post(Query main) 
    
    let go() = 
        init() 
        run() 
        //query() 
    
    관련 문제