数据竞赛?还是其他什么地方出了问题?
Data race? Or something else going wrong?
今天刚找到编程语言"pony"...并开始使用它。
我的代码应该做一些简单的生产者消费者的事情。
正如语言文档所声称的那样,该语言确保没有数据竞争。
在这里,main 向生产者发送 10 条消息,生产者又向消费者发送 10 条消息。消费者增加一个计数器状态变量。然后,main 向消费者发送消息,消费者又向 main 发送消息以显示当前值。如果所有消息都按顺序排列,则预期值为 9(或 10)。打印的结果是 0.
因为这是我玩这门语言的第 1 小时,当然我可能搞砸了其他东西。
谁能解释我的错误?
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]
compiled with: llvm 3.9.0 -- ?
Pony 防止的数据竞争发生在内存级别,当有人从内存位置读取而其他人正在写入它时。这是通过禁止与类型系统共享可变状态来防止的。
但是,如果结果取决于 Pony 无法保证的消息顺序,您的程序仍然可能存在 "logical" 数据竞争。 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
将始终在收到来自 C
的消息之前收到来自 A
的消息,因为 A
会先向 B
发送消息向 C
发送消息(请注意,这两条消息仍然可以按任何顺序接收,因为它们没有相同的目的地)。这意味着 C
发送给 B
的消息在 A
发送给 B
的消息之后发送,并且由于两者具有相同的目的地,因此存在因果关系。
让我们看看你程序中的因果顺序。 ->
为 "is the cause of",我们有
Main.create -> Main.status
(通过 Consumer.query
)
Consumer.create -> Consumer.query
Consumer.create -> Consumer.tick
(通过 Producer.produceOne
)
Producer.create -> Producer.produceOne
如您所见,Consumer.query
和Consumer.tick
之间没有因果关系。在实际实现的意义上,这意味着 Main
可以发送 produceOne
消息,然后在任何 Producer
开始执行它收到的消息之前发送 query
消息有机会发送 tick
消息。如果你 运行 你的程序有一个调度程序线程(--ponythreads=1
作为命令行参数)它将始终打印 produced: 0
因为 Main
将独占唯一的调度程序直到 create
。对于多个调度程序线程,0 到 10 之间的任何事情都可能发生,因为所有调度程序都可能忙于执行其他参与者,或者可以立即开始执行 Producer
s。
总而言之,您的 tick
和 query
行为可以按任何特定顺序执行。要解决此问题,您必须在消息之间引入因果关系,方法是添加往返消息或在同一参与者中进行累积和打印。
感谢@Benoit Vey 的帮助。
确实如此,查询的执行与生产者执行其 tick() 消息传递给消费者的时间之间没有明示或暗示的因果关系。
从这个意义上说,没有巫术,就没有魔法。这一切的行为就像任何演员系统所期望的那样。
Actor 中的消息按顺序处理(应该如此)。因此,为了获得所需的程序行为,生产者最终应该触发查询(在处理 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 ()
今天刚找到编程语言"pony"...并开始使用它。
我的代码应该做一些简单的生产者消费者的事情。 正如语言文档所声称的那样,该语言确保没有数据竞争。
在这里,main 向生产者发送 10 条消息,生产者又向消费者发送 10 条消息。消费者增加一个计数器状态变量。然后,main 向消费者发送消息,消费者又向 main 发送消息以显示当前值。如果所有消息都按顺序排列,则预期值为 9(或 10)。打印的结果是 0.
因为这是我玩这门语言的第 1 小时,当然我可能搞砸了其他东西。
谁能解释我的错误?
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] compiled with: llvm 3.9.0 -- ?
Pony 防止的数据竞争发生在内存级别,当有人从内存位置读取而其他人正在写入它时。这是通过禁止与类型系统共享可变状态来防止的。
但是,如果结果取决于 Pony 无法保证的消息顺序,您的程序仍然可能存在 "logical" 数据竞争。 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
将始终在收到来自 C
的消息之前收到来自 A
的消息,因为 A
会先向 B
发送消息向 C
发送消息(请注意,这两条消息仍然可以按任何顺序接收,因为它们没有相同的目的地)。这意味着 C
发送给 B
的消息在 A
发送给 B
的消息之后发送,并且由于两者具有相同的目的地,因此存在因果关系。
让我们看看你程序中的因果顺序。 ->
为 "is the cause of",我们有
Main.create -> Main.status
(通过Consumer.query
)Consumer.create -> Consumer.query
Consumer.create -> Consumer.tick
(通过Producer.produceOne
)Producer.create -> Producer.produceOne
如您所见,Consumer.query
和Consumer.tick
之间没有因果关系。在实际实现的意义上,这意味着 Main
可以发送 produceOne
消息,然后在任何 Producer
开始执行它收到的消息之前发送 query
消息有机会发送 tick
消息。如果你 运行 你的程序有一个调度程序线程(--ponythreads=1
作为命令行参数)它将始终打印 produced: 0
因为 Main
将独占唯一的调度程序直到 create
。对于多个调度程序线程,0 到 10 之间的任何事情都可能发生,因为所有调度程序都可能忙于执行其他参与者,或者可以立即开始执行 Producer
s。
总而言之,您的 tick
和 query
行为可以按任何特定顺序执行。要解决此问题,您必须在消息之间引入因果关系,方法是添加往返消息或在同一参与者中进行累积和打印。
感谢@Benoit Vey 的帮助。
确实如此,查询的执行与生产者执行其 tick() 消息传递给消费者的时间之间没有明示或暗示的因果关系。
从这个意义上说,没有巫术,就没有魔法。这一切的行为就像任何演员系统所期望的那样。
Actor 中的消息按顺序处理(应该如此)。因此,为了获得所需的程序行为,生产者最终应该触发查询(在处理 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 ()