2017-03-15 55 views
2

就在今天发现了编程语言“小马”......并开始玩它。数据竞赛?还是别的什么问题?

我的代码应该做一些简单的生产者消费者的事情。如语言文档所声明的,该语言确保没有数据竞赛。

这里,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位,顺便说一句。与我发现的最新和最大的zip文件安装。

0.10.0-1c33065 [release] 编译时使用:llvm 3.9.0 - ?

回答

4

Pony阻止的数据竞赛是在内存级发生的,当有人从内存位置读取而其他人正在写入内存级别时。这可以通过禁止与类型系统共享可变状态来阻止。

但是,如果结果取决于Pony无法保证的消息排序,则您的程序仍然可以进行“逻辑”数据竞争。小马保证消息的因果排序。这意味着,如果消息具有相同的目的地,发送或接收的消息是将要发送或接收的任何未来消息的原因,并且当然必须在其效果之前发生。

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将总是从A收到消息从C消息之前因为A将消息发送到B将消息发送到C(请注意,两个消息仍然可以在任何顺序接收之前因为他们没有相同的目的地)。这意味着通过C发送到B的消息在发送到BA的消息之后被发送,并且由于两者具有相同的目的地,所以存在因果关系。

让我们来看看程序中的因果顺序。随着->是 “是的事业”,我们有

  • Main.create -> Main.status(通过Consumer.query
  • Consumer.create -> Consumer.query
  • Consumer.create -> Consumer.tick(通过Producer.produceOne
  • Producer.create -> Producer.produceOne

正如你所看到的, Consumer.queryConsumer.tick之间没有因果关系。从实际意义上讲,这意味着Main可以发送produceOne消息,然后在任何Producer开始执行收到的消息并有机会发送tick消息之前发送query消息。如果使用一个调度程序线程(--ponythreads=1作为命令行参数)运行程序,它将始终打印produced: 0,因为Main将独占唯一调度程序,直到create结束。使用多个调度程序线程,可以发生0到10之间的任何事情,因为所有调度程序都可能正忙于执行其他参与者,或者可以立即开始执行Producer

总之,您的tickquery行为可以按任何特定顺序执行。为了解决这个问题,你必须在你的消息之间引入因果关系,既可以添加往返消息,也可以通过在同一个参与者中进行累积和打印。

+0

似乎我误解了我在各种演示中听到的有关此语言的一些内容。因果消息排序我采用这种方式:从消费者的角度来看,因果消息处理顺序为:勾选(10次),然后查询(一次)。消费者演员应按照该顺序处理消息。如果声称每个参与者都有一个(无界)消息队列,它会从消息队列中提取消息,消费者演员的调度将不得不按照该顺序进行。也许文档应该更清楚地说明演员如何处理消息。 – 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#是相同的。令我惊讶的是,小马在紧凑性方面获胜。 Pony中39行代码,F#中80行。随着本地代码的生成,Pony确实成为一种有趣的语言选择。

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