Skip to content
2010/07/24 / highmt

ひとり勉強会 Axum (前編)

Axum の Programmer’s Guide の前半を読みました。

前半の結論としては、Axumは、アクターモデルに基づいた言語だけどアクターやそのやりとりにいろいろと制約をつけられるところが新しい、のかもしれない。

あと、F#のときとちがって、サンプルに誤植とかがなくちゃんとコンパイルが通って安心。

 

* なぜAxumか
  * 「はやく動かす」いいかえると「時間内によりたくさんのことをさせる」ために並列化をする。
    * たとえばインタラクションを行うプログラムはバックグラウンドでタスクをこなしながら
      ユーザの入力を処理する必要がある。
  * Axumは、並行プログラミングのための言語。
    * 並行性について後から考えたりつけたしたりとかせずに並行化し
      「はやく動く」プログラムを書く。
    * 並行性を扱うためにスレッド間の共有データの操作に制限を与えている。

* Hello World
  * サンプル:
    using System;
    
    agent Program : Microsoft.Axum.ConsoleApplication
    {
      override int Run(String[] args)
      {
        Console.WriteLine("Hello, World!");
      }
    }

  * agent は、「アクターモデル」でいうところのアクターをあらわす。
  * Axumでのプログラミングは agent 間のインタラクションを定義することにほかならない。
  * オブジェクト指向と違うところ:
    * パブリックなメソッドはないし状態を公開することもできない
    * できるのは、メッセージを送ることと返事を待つこと

  * ConsoleApplicationは、.NETが用意しているagent。
    * コンソールアプリケーションに必要な処理(コマンド引数の処理や終了処理など)を実装している


* メッセージパッシング
  * サンプル:
    using System;
    
    agent Program : channel Microsoft.Axum.Application
    {
      public Program()
      {
        // Receive command line arguments from port CommandLine
        String [] args = receive(PrimaryChannel::CommandLine);
        // Send a message to port ExitCode:
        PrimaryChannel::ExitCode <-- 0;
      }
    }

  * channel は、agent がデータをやりとりする通り道をあらわす
    * サンプルでは、agent Program が channel Application を実装している
    * channelを実装する、とは channelの実装側の終端につなぐことを意味する
      * channelには、2つの終端がある
        - 実装側の終端:いわゆるサーバ側につながる終端
        - 使用側の終端:いわゆるクライアント側につながる終端
    * サンプルでは、使用側の終端はAxumランタイムにつながっている
  * port は、channelにメッセージを流すときの出入口をあらわす
    * サンプルでは、Axumランタイムが PrimaryChannel の CommandLine port に
      コマンドライン引数を送り、ExitCode port からのメッセージを待っている
      * PrimaryChannel は組み込みのプロパティで、
        自分が実装している channel Application を指している
      * 「待つ」とは、メッセージを受け取るまで処理がブロックすることを意味する
      * メッセージを送るときは処理はブロックしない
    * agent Program は、channel Application の ExitCode port に
      メッセージを送っている

* メッセージによる非同期処理
  * サンプル:
    using System;
    using Microsoft.Axum;
    using System.Concurrency.Messaging;
    
    agent MainAgent : channel Microsoft.Axum.Application
    {
      function int Fibonacci(int n)
      {
        if( n<=1 ) return n;
        return Fibonacci(n-1) + Fibonacci(n-2);
      }
      int numCount = 10;
      void ProcessResult(int n)
      {
        Console.WriteLine(n);
        if( --numCount == 0 )
          PrimaryChannel::ExitCode <-- 0;
      }
      public MainAgent()
      {
        var numbers = new OrderedInteractionPoint();
        // Create pipeline:
        numbers ==> Fibonacci ==> ProcessResult;
        // Send messages to numbers:
        for( int i=0; i<-- ) i++> Fibonacci ==> ProcessResult という
      データフローネットワークをつくっている
      * ==> は フォワーディングをあらわす
      * 一連のフォワーディングで構成されたデータフローネットワークはパイプラインと呼ばれる

  * functionは、Axumでは、副作用を起こさずに実行できるものをあらわす
    * サンプルの Fibonacci は function なので、
      その中で agent の状態をあらわす numCount を変更しようとするとコンパイルエラーとなる
    * 副作用を起こさずに実行できるので、function は並列に実行することができる
      Axumランタイムは最も効率よく実行できるだけのスレッドを生成する
    * パイプラインで function を使うと、パイプラインの各ノードを並列に実行することができる
    * 一方で パイプライン内のメッセージの順番は保たれる
      * サンプルでは、numbers に入ったメッセージは その順番で PrintResult に届く

* channel と port
  * サンプル:
    using System;
    using System.Concurrency;
    using Microsoft.Axum;
    
    channel Adder
    {
      input int Num1;
      input int Num2;
      output int Sum;
    }

    agent AdderAgent : channel Adder
    {
      public AdderAgent()
      {
        int result = receive(PrimaryChannel::Num1) +
          receive(PrimaryChannel::Num2);
        PrimaryChannel::Sum <-- result;
      }
    }

    agent MainAgent : channel Microsoft.Axum.Application
    {
      public MainAgent()
      {
        var adder = AdderAgent.CreateInNewDomain();
        adder::Num1 <-- 10;
        adder::Num2 <-- 20;
        // do something useful ...
        var sum = receive(adder::Sum);
        Console.WriteLine(sum);
        PrimaryChannel::ExitCode <-- 0;
      }
    }

  * 2つのagent間は、channelによって分断されている
    * 相手がchannelをどのように実装しているかについて知らない
    * agentとchannelの関係は、オブジェクト指向におけるクラスとインタフェースの関係にある
  * agent を channelの使用側から見ると、input port にメッセージを送り
    output port からメッセージを受け取る
  * agent を channelの実装側から見ると、input port からメッセージを受け取り
    output port にメッセージを送る
    * たとえば、channel Adder は、Num1、Num2 という input portを持ち、
      Sum という output port を持つ
    * 使用側は、Num1、Num2 を target として扱い Sum を source として扱う
    * 実装側では、Num1、Num2 を source として扱い Sum を target として扱う


* schema
  * どんなデータでも channel に流せるわけではない
    * deeply serializable でなければならない
      * プロセスや端末をまたがることがあるので
        参照されるすべてのデータがシリアライズされなければならない
    * 2つの agent は並行して共有の変更可能データにアクセスできない
      * メッセージ内のデータが非共有データかまたは変更不可能データでなければ
        agent は並行に実行できない
  * schema は 型の一種で、deeply serializaed かつ immutable なデータをあらわす
    * required フィールドと optional フィールドをもつ
      * 例
        schema Customer
        {
          required string Name;
          optional string Address;
        }
        Customer c = new Customer{Name = "Artur", Address = "One Microsoft Way"};
    * 同じ required フィールドをもつ schema もしくは型は 同じ shape をもつという
    * 同じ shape をもつ schema もしくは型は coerce 演算子によって型変換できる
      * 例
        class CustomerData
        {
          public string Name;
        }
        CustomerData cd = coerce(c);
    * 型変換可能かどうかはコンパイル時にチェックされる

* request-reply port
  * 先の AdderAgent はリクエストを1回受けたら終了してしまう
    * もう一度channelにメッセージを送っても返事は返ってこない
  * リクエストを1回受けたら終わりではなくずっと受け続けられるようにしたい
    * リクエストを受け付けたらクライアントにcorrelatorを返す
    * クライアントはcorrelatorを使ってリクエストに紐付く結果を後で取得する
    * correlatorをサポートするportをrequest-reply portをいう
  * サンプル:
    using System;
    using System.Concurrency;
    using Microsoft.Axum;
    
    schema Pair
    {
      required int Num1;
      required int Num2;
    }
    
    channel Adder
    {
      input Pair Nums : int; // input request-reply port
    }
    
    agent AdderAgent : channel Adder
    {
      public AdderAgent()
      {
        while(true)
          {
            var result = receive(PrimaryChannel::Nums);
            result <-- result.RequestValue.Num1 +
              result.RequestValue.Num2;
          }
      }
    }
    
    agent MainAgent : channel Microsoft.Axum.Application
    {
      public MainAgent()
      {
        var adder = AdderAgent.CreateInNewDomain();
        var correlator1 = adder::Nums <-- new Pair{ Num1=10, Num2=20 };
        var correlator2 = adder::Nums <-- new Pair{ Num1=30, Num2=40 };
        // do something useful here ...
        var sum1 = receive(correlator1);
        Console.WriteLine(sum1);
        var sum2 = receive(correlator2);
        Console.WriteLine(sum2);
        PrimaryChannel::ExitCode <-- 0;
      }
    }

* protocol
  * request-reply portを使っていない最初のAdderの例では、間違って使うと、
    AdderAgentとMainAgentは互いに返事を待ってデッドロックしまう
    * たとえばMainAgentがNum1にだけメッセージを送りSumを待ってしまった場合
  * protocolを使うとchannelが従うべき動作をあらわすことができる
  * protocolは有限ステートマシンとして表現される
    * 初期状態は組み込みの状態Startからはじまる
    * portにメッセージが着いたら状態が遷移する
    * 状態遷移できなければプロトコル違反のエラーになる
    * 組み込みの状態Endに遷移後はプロトコルは終了しているので
      どのportにメッセージを送ってもプロトコル違反となる
  * サンプル
    channel Adder
    {
      input int Num1;
      input int Num2;
      output int Sum;

      Start: { Num1 -> GotNum1; }
      GotNum1: { Num2 -> GotNum2; }
      GotNum2: { Sum -> End; }
    }
  * サンプルのAdderでは、MainAgentがNum1にメッセージを送らずに
    Num2にメッセージを送ろうとするとプロトコル違反になる
  * protocolを厳しくしてもデッドロックを防げないこともある
    * たとえば、サンプルのAdderでは、MainAgentがNum1にメッセージを送った後、
      Num2にメッセージを送らずにSumからのメッセージを待つと
      MainAgentもAdderAgentも待ちになってしまう
    * これはプロトコル違反とはみなされない
      * 誰かがNum2にメッセージを送ればMainAgentもAdderAgentも動き出す

広告
%d人のブロガーが「いいね」をつけました。