【IT168技术文档】
转载自Jeff Zhao的博客
在消息传递(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._
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()