Hello World
Spiga

天下无处不乒乓

2009-06-24 12:46 by 老赵, 21222 visits

在消息传递(Message Passing)领域,PingPong是最常见的测试之一。它的功能简单的有些无聊,一个Ping Actor和一个Pong Actor之间互相传递消息,你Ping过来我Pong过去。也正因为如此简单,PingPong的目标仅仅是测试纯粹的消息传递机制的效率。也正因为如此,各Actor模型往往都将其作为展示自己功能的第一个示例。老赵从互联网上收集了一些最为常见的,不同语言/平台下Actor模型实现PingPong的示例,可作“观赏”之用。

由于语言特性不同,有的Actor模型内置于语言之中的(如Erlang),其他大都由框架进行补充。有的Actor模型使用字符串作为消息,有的却通过语言功能而可以传递强类型的消息。简单的罗列各种实现,把这些实现从表面上进行简单的对比,也可以说颇有趣味。当然,这些实现虽然都使用了PingPong作为演示,但是其中细节还是略有不同的。例如有些会让Ping/Pong过程永远持续下去,而有些会让它们在进行一段时间过后就中止;有些会对命令进行识别,而有些对此并不在意。老赵认为这些区别无伤大雅,对此也未作任何修改。

本文会涉及以下Actor模型的实现(不少语言缺少语法着色,如果您有好用的HTML高亮方案请不吝指明):

在这些示例中,您会发现缺少了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
Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

32 条回复

  1. 代震军
    *.*.*.*
    链接

    代震军 2009-06-24 12:48:00

    不错,够全的了

  2. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-06-24 12:54:00

    我还以为老赵准备开个新的运动专题呢。。。

  3. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-06-24 12:58:00

    光看语言的简洁程度,还是erlang和f#看起来最舒服,不会有很多的大括号和end之类的。

    是不是因为这类语言支持模式匹配?

  4. Old
    *.*.*.*
    链接

    Old 2009-06-24 13:00:00

    先顶了再看
    :-)

  5. AlexLiu
    *.*.*.*
    链接

    AlexLiu 2009-06-24 14:22:00

    这么多 语言啊,什么都会。。猛啊。
    我就知道ping,不消失reponse,
    你的页面的名字是这个。。怎么搞的?
    。。。。2009/06/24/everything-ping-pong.html

  6. 老赵
    admin
    链接

    老赵 2009-06-24 14:24:00

    @AlexLiu
    大部分也就看得懂语法的阶段。
    发表文章时可以填写的

  7. 老赵
    admin
    链接

    老赵 2009-06-24 14:24:00

    @xiao_p
    模式匹配真的很重要,否则很容易碍手碍脚。
    不过其实这些代码还没有体现出价值来,下一次用C#会谈一下的。

  8. AlexLiu
    *.*.*.*
    链接

    AlexLiu 2009-06-24 14:34:00

    我觉得前辈的照片下面把北大青鸟换成某青鸟效果应该也不错。呵呵。

  9. asheng
    *.*.*.*
    链接

    asheng 2009-06-24 14:36:00

    老大,你以前的置顶文章呢?我有个问题请教啊:
    java 开发的jsp 项目 提供web service给我们项目
    只给了我一个wsdl文件,我如何引用到我asp.net项目里啊?
    我的开发环境是 fk3.5+vs2008

  10. Longkin
    *.*.*.*
    链接

    Longkin 2009-06-24 14:45:00

    专门来转下,发现上面的我基本不懂 ,汗

  11. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-06-24 15:29:00

    @Jeffrey Zhao
    真切的感受到老赵在多语言编程和并发方面前进着。

    很早前就看好这几个方向,但是却一直停步不前,programing with Erlang至今依然放在书架上。

  12. 刘荣华![未注册用户]
    *.*.*.*
    链接

    刘荣华![未注册用户] 2009-06-24 16:15:00

    会的语言真多。。厉害,厉害。
    汗,什么Ruby,ErLang,Python都不会。

  13. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-06-24 17:54:00

    老赵你怎么可以会这么多语言呢,天理不容

  14. 老赵
    admin
    链接

    老赵 2009-06-24 17:58:00

    @温景良(Jason)
    我没说我会啊,有些只是接触过而已。而且你会发现,语言很容易看懂的。

  15. Artech
    *.*.*.*
    链接

    Artech 2009-06-24 21:32:00

    Jeffrey是个语言通!

  16. 老赵
    admin
    链接

    老赵 2009-06-25 09:23:00

    @Artech 补充一个自己认为的语言掌握情况吧,大部分语言还是“简单看懂”的阶段。 (见博客右边栏)

  17. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-06-25 09:41:00

    老赵的JavaScript竟然比c#还要精通,那你JavaScript要到什么程度了?

  18. 老赵
    admin
    链接

    老赵 2009-06-25 09:48:00

    @xiao_p
    JavaScript比C#容易掌握啊,当时搞AJAX的时候安心研究过。

  19. 老赵
    admin
    链接

    老赵 2009-06-25 16:14:00

    把图表加到右边栏去了,看上去还不错。

  20. 勇赴
    *.*.*.*
    链接

    勇赴 2009-06-25 16:58:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @xiao_p
    JavaScript比C#容易掌握啊,当时搞AJAX的时候安心研究过。
    --------------------------------------------------------
    说的扯一点,就像武侠里面的牛人,用一根树枝就当剑使,javascript语法规则虽然简单,但想用好其实很难。:)
    不过我是来问问题的:<。老赵,.net Mvc里Views文件夹能不能增加第三层的文件夹呢(默认不是Views下面的文件夹对应控制器嘛,我想再增加一层)?把view都放到第三层文件里,是不是可以通过配置UrlRouting来实现Views文件夹的分层管理.

  21. 老赵
    admin
    链接

    老赵 2009-06-25 17:01:00

    @勇赴
    URLRouting和这个无关,你这个要自定义一个ViewEngine寻找View的方式。

  22. 勇赴
    *.*.*.*
    链接

    勇赴 2009-06-25 19:08:00

    很感谢,回答这么及时

  23. 老赵
    admin
    链接

    老赵 2009-06-26 15:52:00

    @青羽
    谢谢,不过它们生成的代码都好脏啊。

  24. 勇赴
    *.*.*.*
    链接

    勇赴 2009-06-26 16:11:00

    老赵,MVC中的UpdateModel有时不用前缀式会出错,有些Model属性少的不用前缀是可以的,我想应该没这么简单,使不使用前缀式应该跟Model属性多少没有什么关系吧?
    还有一个问题,MVC源码应该从哪开始着手读起呢?那么多代码有种无从下手的感觉:(

  25. 老赵
    admin
    链接

    老赵 2009-06-26 17:10:00

    @勇赴
    这个我就不太清楚了,正好您可以看代码,呵呵。
    这部分代码看DefaultModelBinder就可以了。

  26. 文超
    *.*.*.*
    链接

    文超 2009-06-27 00:00:00

    @勇赴
    不按约定就得自己花点力气了:)

    有URL路由后 Views 不需要要多级文件夹了。
    不过视图多起来,文件夹结构可能不大好。

  27. 勇赴
    *.*.*.*
    链接

    勇赴 2009-06-27 17:04:00

    --引用--------------------------------------------------
    文超: @勇赴
    不按约定就得自己花点力气了:)

    有URL路由后 Views 不需要要多级文件夹了。
    不过视图多起来,文件夹结构可能不大好。
    --------------------------------------------------------
    微软就是怕程序员累,约定的东西不是不好,容易让人懒,呵呵

  28. 老赵
    admin
    链接

    老赵 2009-06-27 17:13:00

    @勇赴
    这个做法不是微软独创的,而是一个适用已久“标准”。

  29. 孟由[未注册用户]
    *.*.*.*
    链接

    孟由[未注册用户] 2009-09-03 09:03:00

    今天刚装了F#,把第一段代码考进去编一下,结果没编过

    错误1 Unexpected yield! in expression. Expected ')' or other token. 30行17列Test

    outbox.Post(Msg(n, inbox)
    return! loop()

    改成

    outbox.Post(Msg(n, inbox))
    return! loop()

    就过了

  30. 小强.假的[未注册用户]
    *.*.*.*
    链接

    小强.假的[未注册用户] 2009-10-28 19:44:00

    这个算PINGPONG不?with ActorLite

    class Program
    {
    static void Main(string[] args)
    {
    System.Threading.Thread t = new System.Threading.Thread(() => Pong.Instance.Post("Ping"));
    t.IsBackground = true;
    t.Start();
    Console.WriteLine(t.ThreadState);
    Console.ReadLine();
    }
    }
    class Pong : Actor<string>
    {
    protected override void Receive(string message)
    {
    Console.WriteLine(message);
    System.Threading.Thread.Sleep(100);
    Ping.Instance.Post("Pong");
    }
    public static readonly Pong Instance = new Pong();
    }
    class Ping : Actor<string>
    {
    protected override void Receive(string message)
    {
    Console.WriteLine(message);
    System.Threading.Thread.Sleep(10000);
    Pong.Instance.Post("Ping");
    }
    public static readonly Ping Instance = new Ping();
    }

  31. LittleLin
    58.114.201.*
    链接

    LittleLin 2010-08-21 12:03:12

    Scala 的例子,似乎少了 case object 的相關宣告?

    case object Ping
    case object Pong
    case object Stop

  32. 小枫
    124.207.249.*
    链接

    小枫 2011-02-15 14:21:07

    弱弱地问个问题:Actor 在C#用哪个命名空间?

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我