天下无处不乒乓
2009-06-24 12:46 Jeffrey Zhao 阅读(20967) 评论(30) 收藏 举报在消息传递(Message Passing)领域,PingPong是最常见的测试之一。它的功能简单的有些无聊,一个Ping Actor和一个Pong Actor之间互相传递消息,你Ping过来我Pong过去。也正因为如此简单,PingPong的目标仅仅是测试纯粹的消息传递机制的效率。也正因为如此,各Actor模型往往都将其作为展示自己功能的第一个示例。老赵从互联网上收集了一些最为常见的,不同语言/平台下Actor模型实现PingPong的示例,可作“观赏”之用。
由于语言特性不同,有的Actor模型内置于语言之中的(如Erlang),其他大都由框架进行补充。有的Actor模型使用字符串作为消息,有的却通过语言功能而可以传递强类型的消息。简单的罗列各种实现,把这些实现从表面上进行简单的对比,也可以说颇有趣味。当然,这些实现虽然都使用了PingPong作为演示,但是其中细节还是略有不同的。例如有些会让Ping/Pong过程永远持续下去,而有些会让它们在进行一段时间过后就中止;有些会对命令进行识别,而有些对此并不在意。老赵认为这些区别无伤大雅,对此也未作任何修改。
本文会涉及以下Actor模型的实现(不少语言缺少语法着色,如果您有好用的HTML高亮方案请不吝指明):
- Erlang (source)
- Scala (source)
- Haskell (source)
- Ruby (source)
- Python (source)
- Axum with Channel (source)
- Axum with Ordered Interaction Points (source)
- F# (source)
- F# with ActorLite (source)
在这些示例中,您会发现缺少了C#下的Actor——因为老赵打算下次单独开篇对此进行说明和介绍。:)
Erlang
-module(tut15).
-export([start/0, ping/2, pong/0]).
ping(0, Pong_PID) ->
Pong_PID ! finished,
io:format("ping finished~n", []);
ping(N, Pong_PID) ->
Pong_PID ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1, Pong_PID).
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
start() ->
Pong_PID = spawn(tut15, pong, []),
spawn(tut15, ping, [3, Pong_PID]).
Scala
import scala.actors.Actor
import scala.actors.Actor._
class Ping(count: int, pong: Actor) extends Actor {
def act() {
var pingsLeft = count - 1
pong ! Ping
loop {
react {
case Pong =>
if (pingsLeft % 1000 == 0)
Console.println("Ping: pong")
if (pingsLeft > 0) {
pong ! Ping
pingsLeft -= 1
} else {
Console.println("Ping: stop")
pong ! Stop
exit()
}
}
}
}
}
class Pong extends Actor {
def act() {
var pongCount = 0
loop {
react {
case Ping =>
if (pongCount % 1000 == 0)
Console.println("Pong: ping "+pongCount)
sender ! Pong
pongCount = pongCount + 1
case Stop =>
Console.println("Pong: stop")
exit()
}
}
}
}
object pingpong extends Application {
val pong = new Pong
val ping = new Ping(100000, pong)
ping.start
pong.start
}
Ruby
require 'concurrent/actors'
include Concurrent::Actors
Message = Struct.new :ping, :pong
ping_thread = Actor.spawn do
loop do
Actor.receive do |f|
f.when Message do |m|
puts "PING"
sleep(1)
m.pong << Message.new(m.ping, m.pong)
end
end
end
end
pong_thread = Actor.spawn do
loop do
Actor.receive do |f|
f.when Message do |m|
puts "PONG"
sleep(1)
m.ping << Message.new(m.ping, m.pong)
end
end
end
end
ping_thread << Message.new(ping_thread, pong_thread)
while(true)
puts("WAITING...")
sleep(5)
end
Python
#
# pingpong_stackless.py
#
import stackless
ping_channel = stackless.channel()
pong_channel = stackless.channel()
def ping():
while ping_channel.receive(): #blocks here
print "PING"
pong_channel.send("from ping")
def pong():
while pong_channel.receive():
print "PONG"
ping_channel.send("from pong")
stackless.tasklet(ping)()
stackless.tasklet(pong)()
# we need to 'prime' the game by sending a start message
# if not, both tasklets will block
stackless.tasklet(ping_channel.send)('startup')
stackless.run()
Axum with Channel
using System; using System.Concurrency; using System.Collections.Generic; using Microsoft.Axum; using System.Concurrency.Messaging; public channel PingPongStatus { input int HowMany; output int Done; Start: { HowMany, Done -> End; } } public agent Ping : channel PingPongStatus { public Ping() { // How many are we supposed to do? var iters = receive(PrimaryChannel::HowMany); // Create pong and send how many to do var chan = Pong.CreateInNewDomain(); chan::HowMany <-- iters; // Send pings and receive pongs for (int i = 0; i < iters; i++) { chan::Ping <-- Signal.Value; receive(chan::Pong); Console.WriteLine("Ping received Pong"); } // How many did Pong process? int pongIters = receive(chan::Done); PrimaryChannel::Done <-- 0; } } public channel PingPong { input int HowMany; output int Done; input Signal Ping; output Signal Pong; } public agent Pong : channel PingPong { public Pong() { // Get how many we're supposed to do var iters = receive(PrimaryChannel::HowMany); int i = 0; // Receive our ping and send back our pong for (; i < iters; i++) { receive(PrimaryChannel::Ping); Console.WriteLine("Pong received Ping"); PrimaryChannel::Pong <-- Signal.Value; } PrimaryChannel::Done <-- i; } } public agent Program : channel Application { public Program() { // Wait for our command args var cmdArgs = receive(PrimaryChannel::CommandLine); // Set the iterations to be some number var iters = 3; // Create instance of ping and send msg var chan = Ping.CreateInNewDomain(); chan::HowMany <-- iters; // Receive how many done receive(chan::Done); PrimaryChannel::Done <-- Signal.Value; } }
F#
open System type message = Finished | Msg of int * MailboxProcessor<message> let ping iters (outbox : MailboxProcessor<message>) = MailboxProcessor.Start(fun inbox -> let rec loop n = async { if n > 0 then outbox.Post( Msg(n, inbox) ) let! msg = inbox.Receive() Console.WriteLine("ping received pong") return! loop(n-1) else outbox.Post(Finished) Console.WriteLine("ping finished") return ()} loop iters) let pong () = MailboxProcessor.Start(fun inbox -> let rec loop () = async { let! msg = inbox.Receive() match msg with | Finished -> Console.WriteLine("pong finished") return () | Msg(n, outbox) -> Console.WriteLine("pong received ping") outbox.Post(Msg(n, inbox) return! loop() } loop()) let ponger = pong() do (ping 10 ponger) |> ignore
F# with ActorLite
#light open System open ActorLite let (<=) (m:_ Actor) msg = m.Post msg type message = string * message Actor let pong = { new message Actor() with override self.Receive(message) = let msg, ping = message Console.WriteLine(msg) ping <= ("Pong", self) } let ping = { new message Actor() with override self.Receive(message) = let msg, pong = message Console.WriteLine(msg) pong <= ("Ping", self) } ping <= ("Start", pong) Console.ReadLine |> ignore
浙公网安备 33010602011771号