数据竞争?或者其他什么问题?

时间:2017-03-15 21:22:07

标签: ponylang

今天就找到了编程语言“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上运行,顺便说一下。我找到了最新最好的zip文件安装。

  

0.10.0-1c33065 [发布]   编译:llvm 3.9.0 - ?

2 个答案:

答案 0 :(得分:5)

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

但是,如果结果取决于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(请注意,仍然可以按任何顺序接收这两条消息,因为它们没有相同的目的地)。这意味着B发送给C的邮件是在B发送给A之后发送的,并且由于两者具有相同的目的地,因此存在因果关系。

让我们看看你的程序中的因果排序。 ->是“原因”,我们有

  • 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消息,然后在任何query开始执行收到的消息之前发送Producer消息,并且有机会发送tick消息。如果使用一个调度程序线程(--ponythreads=1作为命令行参数)运行程序,它将始终打印produced: 0,因为Main将独占调度程序,直到create结束。使用多个调度程序线程时,可能会发生0到10之间的任何事情,因为所有调度程序可能忙于执行其他actor,或者可以立即开始执行Producer

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

答案 1 :(得分:1)

感谢@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 ()