Spiga

天下无处不乒乓

2009-06-24 12:46 by Jeffrey Zhao, 19286 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
标签: PingPong, Actor
Add your comment

30 条回复

  1. #1楼 代震军      2009-06-24 12:48
    不错,够全的了
     回复 引用 查看   
  2. #2楼 xiao_p      2009-06-24 12:54
    我还以为老赵准备开个新的运动专题呢。。。
     回复 引用 查看   
  3. #3楼 xiao_p      2009-06-24 12:58
    光看语言的简洁程度,还是erlang和f#看起来最舒服,不会有很多的大括号和end之类的。

    是不是因为这类语言支持模式匹配?
     回复 引用 查看   
  4. #4楼 Old      2009-06-24 13:00
    先顶了再看
    :-)
     回复 引用 查看   
  5. #5楼 AlexLiu      2009-06-24 14:22
    这么多 语言啊,什么都会。。猛啊。
    我就知道ping,不消失reponse,
    你的页面的名字是这个。。怎么搞的?
    。。。。2009/06/24/everything-ping-pong.html
     回复 引用 查看   
  6. #6楼[楼主] Jeffrey Zhao      2009-06-24 14:24
    @AlexLiu
    大部分也就看得懂语法的阶段。
    发表文章时可以填写的
     回复 引用 查看   
  7. #7楼[楼主] Jeffrey Zhao      2009-06-24 14:24
    @xiao_p
    模式匹配真的很重要,否则很容易碍手碍脚。
    不过其实这些代码还没有体现出价值来,下一次用C#会谈一下的。
     回复 引用 查看   
  8. #8楼 AlexLiu      2009-06-24 14:34
    我觉得前辈的照片下面把北大青鸟换成某青鸟效果应该也不错。呵呵。
     回复 引用 查看   
  9. #9楼 asheng      2009-06-24 14:36
    老大,你以前的置顶文章呢?我有个问题请教啊:
    java 开发的jsp 项目 提供web service给我们项目
    只给了我一个wsdl文件,我如何引用到我asp.net项目里啊?
    我的开发环境是 fk3.5+vs2008
     回复 引用 查看   
  10. #10楼 Longkin      2009-06-24 14:45
    专门来转下,发现上面的我基本不懂 ,汗
     回复 引用 查看   
  11. #11楼 xiao_p      2009-06-24 15:29
    @Jeffrey Zhao
    真切的感受到老赵在多语言编程和并发方面前进着。

    很早前就看好这几个方向,但是却一直停步不前,programing with Erlang至今依然放在书架上。
     回复 引用 查看   
  12. #12楼 刘荣华![未注册用户]2009-06-24 16:15
    会的语言真多。。厉害,厉害。
    汗,什么Ruby,ErLang,Python都不会。
     回复 引用   
  13. #13楼 温景良(Jason)      2009-06-24 17:54
    老赵你怎么可以会这么多语言呢,天理不容
     回复 引用 查看   
  14. #14楼[楼主] Jeffrey Zhao      2009-06-24 17:58
    @温景良(Jason)
    我没说我会啊,有些只是接触过而已。而且你会发现,语言很容易看懂的。
     回复 引用 查看   
  15. #15楼 Artech      2009-06-24 21:32
    Jeffrey是个语言通!
     回复 引用 查看   
  16. #16楼[楼主] Jeffrey Zhao      2009-06-25 09:23
    @Artech 补充一个自己认为的语言掌握情况吧,大部分语言还是“简单看懂”的阶段。 (见博客右边栏)
     回复 引用 查看   
  17. #17楼 xiao_p      2009-06-25 09:41
    老赵的JavaScript竟然比c#还要精通,那你JavaScript要到什么程度了?
     回复 引用 查看   
  18. #18楼[楼主] Jeffrey Zhao      2009-06-25 09:48
    @xiao_p
    JavaScript比C#容易掌握啊,当时搞AJAX的时候安心研究过。
     回复 引用 查看   
  19. #19楼[楼主] Jeffrey Zhao      2009-06-25 16:14
    把图表加到右边栏去了,看上去还不错。
     回复 引用 查看   
  20. #20楼 勇赴2009-06-25 16:58
    --引用--------------------------------------------------
    Jeffrey Zhao: @xiao_p
    JavaScript比C#容易掌握啊,当时搞AJAX的时候安心研究过。
    --------------------------------------------------------
    说的扯一点,就像武侠里面的牛人,用一根树枝就当剑使,javascript语法规则虽然简单,但想用好其实很难。:)
    不过我是来问问题的:<。老赵,.net Mvc里Views文件夹能不能增加第三层的文件夹呢(默认不是Views下面的文件夹对应控制器嘛,我想再增加一层)?把view都放到第三层文件里,是不是可以通过配置UrlRouting来实现Views文件夹的分层管理.
     回复 引用   
  21. #21楼[楼主] Jeffrey Zhao      2009-06-25 17:01
    @勇赴
    URLRouting和这个无关,你这个要自定义一个ViewEngine寻找View的方式。
     回复 引用 查看   
  22. #22楼 勇赴2009-06-25 19:08
    很感谢,回答这么及时
     回复 引用   
  23. #23楼[楼主] Jeffrey Zhao      2009-06-26 15:52
    @青羽
    谢谢,不过它们生成的代码都好脏啊。
     回复 引用 查看   
  24. #24楼 勇赴2009-06-26 16:11
    老赵,MVC中的UpdateModel有时不用前缀式会出错,有些Model属性少的不用前缀是可以的,我想应该没这么简单,使不使用前缀式应该跟Model属性多少没有什么关系吧?
    还有一个问题,MVC源码应该从哪开始着手读起呢?那么多代码有种无从下手的感觉:(
     回复 引用   
  25. #25楼[楼主] Jeffrey Zhao      2009-06-26 17:10
    @勇赴
    这个我就不太清楚了,正好您可以看代码,呵呵。
    这部分代码看DefaultModelBinder就可以了。
     回复 引用 查看   
  26. #26楼 文超      2009-06-27 00:00
    @勇赴
    不按约定就得自己花点力气了:)

    有URL路由后 Views 不需要要多级文件夹了。
    不过视图多起来,文件夹结构可能不大好。
     回复 引用 查看   
  27. #27楼 勇赴2009-06-27 17:04
    --引用--------------------------------------------------
    文超: @勇赴
    不按约定就得自己花点力气了:)

    有URL路由后 Views 不需要要多级文件夹了。
    不过视图多起来,文件夹结构可能不大好。
    --------------------------------------------------------
    微软就是怕程序员累,约定的东西不是不好,容易让人懒,呵呵
     回复 引用   
  28. #28楼[楼主] Jeffrey Zhao      2009-06-27 17:13
    @勇赴
    这个做法不是微软独创的,而是一个适用已久“标准”。
     回复 引用 查看   
  29. #29楼 孟由[未注册用户]2009-09-03 09:03
    今天刚装了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. #30楼 小强.假的[未注册用户]2009-10-28 19:44
    这个算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();
    }
     回复 引用   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1509713 60UeNMZNXI4=