Skip to content
2010/07/26 / highmt

Axum ひとり勉強会 (後編)

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

後半は、domainやらhostingやらasynchronousやらといった、わかりそうでわかりにく概念が出てきます。Axum Language Spec や Axum Team Blog などで補完しないと Programmer’s Guide だけでは理解が厳しい…。

* agent間の状態の共有
  * domain は、あるagentのグループだけが状態を安全に共有できるようにする
  * 例
    domain Chatroom
    {
      private string m_Topic;
      private int m_UserCount;
      reader agent User : channel UserCommunication
      {
        // ...
      }
      writer agent Administrator : channel AdminCommunication
      {
        // ...
      }
    }

* Reader-Writer セマンティクス
  * Reader-Writer ロックでは、共有リソースに対して、
    複数の読み取りかまたは単一の書き込みのいずれかだけを同時には許可しない
    ことによって、各読み取りで読まれる状態が一貫性をもつことと
    書き込みが互いに干渉しないことを保証する
  * domain で宣言された reader と writer もReader-Writerセマンティクスにならう
    * reader と宣言された agent は domain の状態を読むことだけできる
    * writer と宣言された agent は domain の状態を読むことも書くこともできる
    * どちらでもない agent は domain の immutable な状態だけを読むことができる
  * agent 内では parent キーワードを使って agent を囲む domain にアクセスする
    * ただしparentをつけなくてもいい
  * 例
    reader agent User : channel UserCommunication
    {
      public User()
      {
        if( parent.m_Topic.Contains("Axum") )
          {
            // Start talking to other users in this chatroom
          }
        else
          {
            // Do nothing and leave
          }
      }
    }
  * reader が domain の状態を変更するようなコードはコンパイルできない
  * 例
    if( parent.m_Topic.Contains("Axum") )
      {
        // Start talking to other
        // users in this chatroom
      }
    else
      {
        // Let’s talk about Axum!
        parent.m_Topic = "Axum Discussion";
      }
    
  * unsafe キーワードつけると domain の制約を回避できる
  * 例
    reader agent User : channel UserCommunication
    {
      private WriteLog(string str)
      {
        unsafe
          {
            Logger.WriteLine(str);
          }
      }
    }

* hosting agent
  * Adderの例で出てきた以下のコードは、
    新しい domain をつくり、それを parent とする agent をつくる
    ということを意味している
      var adder = AdderAgent.CreateInNewDomain();
  * CreateInNewDomain は新しい domain をつくるので
    agent 間で domain を共有できない
  * domain の特定のインスタンスに agent を関連づける方法を hosting という
  * hosting するには以下のようなコードを書く
      Host("my adder");
    * 'my adder' というアドレスに host されている Adder サービスを使うときは
      AdderAgent をインスタンス化して現在の domain に結び付け、
      そのAdderAgentの実装する channel の使用側の終端を返す、
      という意味になる
      * アドレスは、同一性比較ができるオブジェクトであれば何でもよい
      * Host は domain のメソッドなので
        domain の文脈の中でのみ使用できる
    * 以下のようにして agent をインスタンス化できる
        var adder = new Adder("my adder");
      * 'my adder' というアドレスで host されているサービスによって
        実装されている channel の 使用側の終端を作成する、
        という意味になる
  * 例
    channel Fibonacci
    {
      //...
    }

    domain F
    {
      public F()
      {
        Host("slow");
        Host("fast");
      }

      agent FibonacciSlow : channel Fibonacci
      {
        //...
      }

      agent FibonacciFast : channel Fibonacci
      {
        //...
      }

      agent MainAgent : channel Microsoft.Axum.Application
      {
        public MainAgent()
        {
          Fibonacci fibChannel;
          if(gotTimeToSpare)
            {
              fibChannel = new Fibonacci("slow");
            }
          else
            {
              fibChannel = new Fibonacci("fast");
            }
          var request = fibChannel::Number <-- 42;
          //...
        }
      }
    }

    * channel Fibonacci を実装する agent をアドレスによって選択できる
  * 不要になった agent は domain から削除できる
    * 例
      Evict("slow");
      Evict("fast");
    * 削除後は そのアドレスでnewを使って agent をインスタンス化できなくなる

* データフローネットワークによるプログラミング
  * 1対1
    * forward
  * 多対1
    * multiplex
    * combine
  * 1対多
    * broadcast
    * alternate

* 1対1 forward
  * forward オペレータ: ==>
    * source で生成されたメッセージを target に転送する
  * forward once オペレータ: -->
    * source で生成されたメッセージを target に1回だけ転送する
      転送後切断する
  * forward はパイプラインをつくる用途以外に
    イベントドリブンシステムをつくる用途にも使われる
    * 例
      channel GUIChannel
      {
        // MouseEvent is an enum with values Up and Down
        input MouseEvent Click;
        // Key describes which key was pressed
        input Key KeyPress;
        // Rect is a rectangle to repaint
        input Rect Paint;
      }

      agent GUIHandler : channel GUIChannel
      {
        public GUIHandler()
        {
          PrimaryChannel::Click ==> HandleClick;
          PrimaryChannel::KeyPress ==> HandleKeyPress;
          PrimaryChannel::Paint ==> HandlePaint;
        }
        void HandleClick(MouseEvent mouseEvent)
        {
          ...
        }
        void HandleKeyPress (Key key)
        {
          ...
        }
        void HandlePaint (Rect rect)
        {
          ...
        }
      }

* 多対1 multiplex と combine
  * source のベクタ と 1つの taget をつなぐ
  * multiplex オペレータ: >>-
    source のベクタのいずれかの source で生成されたメッセージを生成される度に target に送る
  * combine オペレータ: &>-
    source のベクタのすべての source でメッセージが生成されるの待ち 配列にして target に送る
  * 例
    void PrintOneNumber(int n)
    {
      Console.WriteLine(n);
    }
    void PrintManyNumbers(int[] nums)
    {
      foreach(var i in nums)
        Console.WriteLine(i);
    }

    //...
      var ip1 = new OrderedInteractionPoint();
      var ip2 = new OrderedInteractionPoint();
      ip1 <-- 10;
      ip2 <-- 20;
      var ips = new OrderedInteractionPoint[] { ip1, ip2 };
      ips >>- PrintOneNumber;

    //...
      var ip1 = new OrderedInteractionPoint();
      var ip2 = new OrderedInteractionPoint();
      ip1 <-- 10;
      ip2 <-- 20;
      var ips = new OrderedInteractionPoint[] { ip1, ip2 };
      ips &>- PrintManyNumbers;

  * 例
    receive( { ip1, ip2 } &>- twoNumbers );

* 1対多 broadcast と alternate
  * 1つの source から 複数の target にメッセージを送る
  * broadcast オペレータ: -<<
    * メッセージをすべての target にコピーする
    * publisher-subscriber シナリオで使用できる
  * alternate オペレータ: -<:
    * メッセージを各targetにラウンドロビン方式で送る
    * ロードバランシングで使用できる
  * 例
    agent AdderAgent : channel Adder
    {
      public AdderAgent()
      {
        var ipJoin = new OrderedInteractionPoint();
        { PrimaryChannel::Num1 ==> ShowNumber, PrimaryChannel::Num2 ==> ShowNumber }
          &>- ipJoin -<: { GetSum, GetSum } >>- PrimaryChannel::Sum;
      }
      private int ShowNumber(int n)
      {
        Console.WriteLine("Got number {0}", n);
        return n;
      }
      private function int GetSum(int[] nums)
      {
        return nums[0] + nums[1];
      }
    }

    * 並行実行が可能な function である GetSum に alternate することで
      パフォーマンスの向上を図る

* Axum 分散アプリケーション
  * Axum は分散アプリケーションをサポートしている
  * domain : SOAサービス、agent : プロトコルハンドラ、schema : ペイロード定義、
    と考えると Axum アプリケーションはWebサービスにマップすることができる
  * Axum ランタイムは、ローカル/in-process の channel と リモート/WCFベースの
    channel をサポートしている
  * domain の中の agent にアクセスするには、ローカルの場合もリモートの場合も
    アドレスが必要となる
  * Axum ランタイムは、IHost インタフェースを通じてアドレスを使った agent への
    アクセスをサポートしている
    * アドレスは ある型の agent のファクトリに結び付けられる
    * ファクトリは新しいコネクションをつくるときに agent のインスンタスをつくる
    * Axum は、IHostの実装としてWCF用とin-process用の2つをはじめからもっている
  * アドレスは、既にある domain のインスタンスと結び付けられる場合もあるし
    結び付けられた domain のインスタンスを持たない場合もある
    * 前者は、agent のインスタンスをつくると、そのアドレスに結び付けられている
      domain に結び付けられる
    * 後者は、agent のインスタンスをつくると、domain の新しいインスタンスに
      結び付けられる
  * domain を サービスとして公開する Axum ベースのサーバの例
      channel Simple
      {
        input string Msg1;
        output string Msg2;
      }
      
      domain ServiceDomain
      {
        agent ServiceAgent : channel Simple
        {
          public ServiceAgent ()
          {
            // Do something useful.
          }
        }
      }
      
      agent Server : channel Microsoft.Axum.Application
      {
        public Server ()
        {
          var hst = new WcfServiceHost(new NetTcpBinding(SecurityMode.None, false));
          hst.Host("net.tcp://localhost/Service1");
        }
      }

    * クライアントが net.tcp://localhost/Service1 に接続するたびに
      新しいServiceDomainに結び付けられたServiceAgentのインスタンスが作成される
    * 特定の1つのServiceDomainインスタンスと結び付けられたServiceAgentが作成される
      ようにするには以下のようにする
      hst.Host("net.tcp://localhost/Service1");
    * 正しいWCFバインディングを選ぶだけでよい
      あとはWCFが全部やってくれるのですぐにできる
    * schema を使えば DataContract に従う安全な通信ができる

  * 分散アプリケーションの作成は、プロセス内のプログラミングとまったくかわらない
    * 同じプログラミングモデル
    * IHost/ICommunicationProviderの実装が異なるだけ
    * agent にアクセスするためのアドレスの見え方が違うだけ

* asynchronous メソッド
  * see http://blogs.msdn.com/b/maestroteam/archive/2009/05/23/yielding-with-asynchronous-methods.aspx
    * メソッドの実行はトークンによって同時実行が制御されている
    * ブロックしないとは、他のメソッドにトークンを渡すこと
  * agent の constructorはデフォルトでasynchronous
    それ以外は明示的にaynchronousと指定したときにasynchronous
  * 以下はブロックする
    * receive(式または文)
    * .NET の Monitor.Enter
    * .NET の 同期I/O
  * asynchronous を指定すると
    * receive(式または文)はブロックしなくなる
    * .NET の APM(Asynchronous Programming Model) に従っているメソッド
      (対応するBeginXXX、EndXXXをもつXXX)は非同期版が使用される
  * asynchronous のルール
    * receive(式まはた文)を呼び出すメソッドはasynchronousであるべき
    * APMのAPIを呼び出すメソッドはasynchronousであるべき
    * asynchronousメソッドを呼び出すメソッドはasynchronousであるべき

* クラス定義
  * Axum言語ではクラスを定義できない
  * 別途他のCLR言語でコンパイル済のクラスを参照することはできる
  * Axumは実験版C#コンパイラをもっておりこれを利用してクラスをコンパイルできる

* 副作用
  * 実験版C#コンパイラでは isolated キーワード と readonly キーワードを処理できる
  * isolated キーワードのついたクラスまたはメソッドは
    static フィールド を変更できない
  * Axumでは isolated なメソッドだけを安全に使用できる
  * 同様に、非staticフィールドを変更しないことを readonly でマークできる
  * domain のフィールドのメソッドを呼び出すときにデータ競合を起こさないことを
    readonlyで保証できる
  * 製品版C#コンパイラでは、TseCorelib.dllに定義されている
    [Strict]属性と[Readable]属性で同様のことができる

* contract assembly
  * コンパイル時にアセンブリを参照する際に追加される情報を保持する
  * contract assembly は [assembly: System.Diagnostics.Effects.ContractAssembly]属性で
    識別される
  * Axumでは TseContracts.dll がcontract assemblyとして付属している
    * これは BCL(.NET Base Class Library)に対するcontract assemblyになっている
    * AxumではTseContracts.dllが暗黙に参照される
  * contract assemblyで付与される属性はコンパイラによって検証されるわではない
    * 正しい属性をつけることは作成者の責務

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