天下に卓球をしないところはない

18306 ワード

メッセージング(Message Passing)の分野では、PingPongは最も一般的なテストの一つです.その機能は簡単で少し退屈で、1つのPing Actorと1つの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
    
    let ping iters (outbox : MailboxProcessor) =
        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