タプル空間、Linda、JavaSpaces、Ruby Linda(Rinda)

並行システム

                               システム情報系情報工学域,
			       システム情報工学研究科コンピュータサイエンス専攻
                               新城 靖
                               <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26
あるいは、次のページから手繰っていくこともできます。
http://www.cs.tsukuba.ac.jp/~yas/cs/
http://www.cs.tsukuba.ac.jp/~yas/

■今日の重要な話

並列プログラムの作り方 資料

■協調作業のための基本パラダイム

-- 並列/分散プログラムのパタン。

◆並列プログラムを構築する手順

  1. パラダイムを選ぶ
  2. パラダイムに合った手法(Methods)で書く
  3. プログラム変換する
性能が十分でない場合には、他のパラダイムを使って書き直す

◆パラダイム1:結果並列法(Result Parallelism)

並列処理の最終結果に焦点を当る。

例: 家の建築:

◆パラダイム2:専門家並列法 (Specialist Parallelism)

並列処理を行う作業者に焦点を当る。

例: 家の建築:

◆パラダイム3:手順並列法 (Agenda Parallelism)

並列処理の手順に焦点をあてる 例: 家の建築:

◆どのパラダイムを使うか

問題にあったものを使う。

実際の家の建築では、全部の方法が使われている。

◆結果並列法の使い方

簡単。 結果のデータ構造を得るための依存関係が予めわかっている時に便利。

うまく行く例:ベクトルの足し算: S[i] = A[i] + B[i]

  1. n 要素のベクトルを作る
  2. S[i] は、A[i] と B[i] を加えればわかる。
出力データが不明なもの、結果が1つのもには適していない。
テキスト処理、コンパイラ、
結果が可変
LU分解
繰り返しがある
OS、実時間
結果というよりは作用が大事

◆手順並列法の使い方

マスタ・ワーカ
  1. マスターが計算を始める
  2. 同一のワーカ・プロセスの集合を作る
  3. ワーカ、どの仕事でもやる。
  4. 仕事がなくなれば終わり
うまく行く例: データベースの項目から、ある関数の値が最小のものを探す

  1. マスターは、袋(task bag)の中にデータ・レコードを全部入れる
  2. 各ワーカは、袋から取り出して(複雑な)関数の計算を行い、 結果をマスターに返す
  3. マスターは、ワーカから送られてきたものの最小のものを記録する。
  4. 全部が終われば終わり。

タスクバッグ、ワーカ、タスク

図? タスクバッグ

◆専門家並列法の使い方

プロセスのネットワークが作られる 例:輸送時間見積もり
プロセス
道路(都市)
データ
トラック
道路が並列。複数のデータが流れる。

◆その他の並列パラダイム

■プログラミングの手法

視点

◆手法1:ライブデータ構造体(Live Data Structure)

関数型言語は、これ。

データの中にプロセスが活動する。

図? ライブデータ構造体

◆手法2:メッセージパッシング(Message Passing)

マルチスレッドの特殊な場合。ロックを使わずに、TSD (Thread Specific Data) と通信だけでプログラムを書く場合はこれ。

プロセス中にデータがある。

図? メッセージ・パッシング

◆手法3:分散データ構造体(Distributed Data Structure)

共有メモリのマルチスレッドは、TSD (Thread Specific Data) をのぞいてだいたいこれ。

データとプロセスが独立。

図? 分散データ構造体

◆N-体問題シミュレータ

N個の物体の位置をq時間単位分計算する。

◆N-体問題:結果並列法

ライブデータ構造体の図 参照。

位置の配列: M[i][j]、i=0..N-1, j=0..q、M[][0] に最初の位置。

position(i,j), 繰り返し j での 物体 i の位置を計算する。 その時、一つ前ののステップの位置 position(i,j-1) (i=0..N-1) の処理が終了するのを待つ。

プロセスは、position(i,j) の計算をするとデータに化けて行く。 ライブデータ構造体の図 で、 赤い丸は活動中のプロセス。黒い丸は終了したプロセス。

◆N-体問題:専門家並列法

物体に対応したプロセスを作る。

◆N-体問題:手順並列法

手順(ワーカの仕事):集合に含まれている全ての物体について、次の位置を 計算する。

マスタで、N 個の物体を作る。

ワーカを作る。ワーカの数は、N 個ではなくて、もっと少ない(CPU数と同じに する)。

物体の位置を分散データ構造体(共有メモリ)に置く。

◆並列プログラムを書く方法

  1. 問題に適したパラダイムを選ぶ
  2. パラダイムに適した手法でプログラムを書く
  3. 必要なら効率のよい手法で書き直す
(粒度の調整)

◆手法の相互変換

分散データ構造体、ライブデータ構造体、メッセージパッシング

図? 手法の変換

アブストラクション(abstraction)
データとプロセスの結び付きを切る
スペシャリゼーション(specialization)
データとプロセスから分離して分散データ構造体におく
クランピング(clumping)
通信を明示的に行うようにする
デクランピング(declumping)
通信を暗黙的にに行うようにする
問題1: ライブデータ構造体でプログラムを書いたらプロセス生成が多くて 性能が出ない。

解決1:ライブデータ構造体を、受動的な構造体に書き換える。プロセスを複数 の構造体に対応させる。

問題2:分散データ構造体で書いたプログラム(共有空間が必要)が、NORMA で うまく動かない。

解決:メッセージ・パッシングに変換する。

Carriero と Gelernter の主張:分散データ構造体がいい。

Java では、スレッド、RMI、Javaspaces の順に導入された。

◆プログラミング言語とライブラリ

1. ライブデータ構造体
データフロー: Id Nouveau
関数型言語: Multilisp, Sisal
2. 分散データ構造体
言語:HPF (High Performance Fortran)
共有メモリとロック/セマフォ
DSM: Orca, Munin, Midway, Linda
3. メッセージパッシング
特殊言語:CSP/Occam
RPC、ランデブ:Ada
ライブラリ:PVM, MPI
モニタ:Concurrent Pascal, Modula

モニタは、メッセージパッシングの仲間か分散データ構造体か。

■Linda

協調言語(<−>計算言語)。

タプルペースモデル(tuple space model)で分散データ構造体を支援。(メッ セージ・パッシングやライブデータ構造体的なプログラムも書ける。)

◆タプル空間(tuple space)

タプルの集合である。

タプルは、型付きの値の並び。

タプルの例:

("a string", 10.01, 17, 10)

(0,1)

2種類のタプル

データタプル
受動的
ライブタプル(プロセスタプル)
全部が同時に実行している。 データタプルを読み書きする。 終了するとデータタプルに変化する。

◆NUMAとタプル空間

速いローカルメモリと遅い大域的な共有メモリ。 非均質共有メモリ型マルチプロセッサ参照。

◆Lindaの命令

タプル空間を操作する基本4命令
out()
タプルをタプル空間内に生成する。(受動的なタプル)
in()
タプルを取り去る
rd() -- (read)
in と似ているが、タプルがタプル空間に残る。
eval()
out() と似ているが、新たにプロセスが作られて、そのプロセスによりタ プルが評価される。(ライブタプル)
predicate つき(デットロック対策で使う)
inp()
in() と同じだが、見つからなければ 0 を返す
rdp()
rd() と同じだが、見つからなければ 0 を返す

◆formalとactual

out("a string", 10.01, 17, x)

in("a string", ?f, ?i, y)

「?」付のものは、formal。型が同じものとマッチする。 ついていないものは、actual。型と値が同じものとマッチする。

◆プロセス生成

eval("e", 7, exp(7))
新しくプロセス(スレッド)が作られ、exp(7) を計算しはじめる。プロセスが 終了すると、最終結果は、値に変わり、out() されたのと同じになる。

◆同期

in() と out() で同期がとられる。out() はブロックしない。in() は条件に よりブロックする。
out() してから in()
in() したプロセスはブロックしない
out() する前に in()
in() したプロセスはブロックする。 out() されるとブロック解除。

◆基本分散データ構造体

コンテナの分類
  1. 要素が全て同じ、または、要素を区別して取り出す方法がない。 タスクバッグ。集合。
  2. 要素が名前で区別できる。構造体、ハッシュ表。
  3. 要素が位置て区別される。

◆名前付の構造体(連想メモリ)

スクリプト言語(perl,ruby,awkなど)の連想配列と同じ。

タプルの形式: (name,val)

読込み:
    rd(name,?val)

変更:
    in(name,?val)
    val = val + 1 ;
    out(name,val)

◆計数セマフォ

P命令: 
    in("sem-1")

V命令:
    out("sem-1");
同じタプルを out したら溜る。

注意:in() したデータは、1プロセスでしかアクセスされないので、セマフォ などによるロックは不要。

◆Task Bag

仕事を入れる:
    out("task",TaskDescription)

仕事を取り出す:
    in("task",?NewTask)

◆並列DOループ

逐次:
for( i=0 ; i<N; i++ )
{
    func(i,args);
}
並列:
for( i=0 ; i<N; i++ )
{
    eval ("loop-33", func(i,args) ); // プロセス生成, fork
}
for( i=0 ; i<N; i++ )
{
    in("loop-33", 1 ); // 待ち, join
}

func(i,args)
{
   ...
   return( 1 );
}
これは並列処理の fork-join モデルを実現した物。

single-fork==multi==join-single-thread

図? fork-joinモデル

◆バリア同期

全部のプロセスが到着してから次に進む。

マルチスレッドの マスタ・スレーブ(バリア付き) 参照。

n プロセスのバリア

初期化:
    out("barrier-37",n)
各プロセス: 1減らして、0になるのを待つ。
    in("barrier-37",?val)
    out("barrier-37",val-1)
    rd("barrier-37",0)

single-fork==multi==join-single-thread

図? fork-joinの繰り返し

single-fork===barrier==barrier===barrier===

図? fork-joinの繰り返し

◆配列

A[10];

("A",0,val00)
("A",1,val01)
("A",2,val02)
...
("A",9,val99)
2次元配列の場合。
A[10][10];

("A",0,0,val00)
("A",0,1,val01)
("A",0,2,val02)
...
("A",9,9,val99)

◆ストリーム(inストリーム)

1人が読むとデータが消える。
ストリームデータ
    ("stream",0,val0)
    ("stream",1,val1)
    ("stream",2,val2)
    ...

ポインタ
    ("stream","head",0)
    ("stream","tail",0)

ストリームに要素を追加:
    int index; // ローカル変数
    in("stream","tail",?index);
    out("stream","tail",index+1);
    out("stream",index,new_element);

ストリームから要素を取り出す:
    int index; // ローカル変数
    in("stream","head",?index);
    out("stream","head",index+1);
    in("stream",index,?element);
これは、複数source、複数sink。

source、sinkが1つなら、head, tail をタプル空間に置かなくてもよい。

◆ストリーム(readストリーム)

1つのストリームを、複数の読み手でアクセスできる(マルチキャスト)。

head の代わりに、局所変数でアクセスする。

ストリームから要素を取り出す:
    int index=0 ;
    while( ... )
    {
       rd("stream",index++,?element);
    }
rd() しかなされず、out() する人がいないので、タプル空間にストリームのデー タが残ってしまう。

◆メッセージ・パッシングのプログラムの書き方

◆ライブ・データ構造体のプログラムの書き方

■JavaSpaces

Linda のタプル空間 の考え方を Java で実現したもの。

interface JavaSpace を実現したオブジェクト
Linda JavaSpace 説明
out write タプルをタプル空間内に生成する。
in take タプルを取り去る
rd read in/takeと似ているが、タプルがタプル空間に残る。
ライブタプル(eval命令)は、ない。

write, read, take を使う部分のプログラムは簡単だが、space を利用可能に するのには苦労する。

JavaSpaces が提供する空間の特徴

◆interface Entry

空間に置くことができるオブジェクトは、interface Entry を implements し たもの。

net/jini/core/entry/Entry.java:
public interface Entry extends java.io.Serializable {
}

◆interface JavaSpace

net/jini/space/JavaSpace.java:
public interface JavaSpace {
    Lease write(Entry entry, Transaction txn, long lease)
        throws TransactionException, RemoteException;
    long NO_WAIT = 0;
    Entry read(Entry tmpl, Transaction txn, long timeout)
        throws UnusableEntryException, TransactionException, 
               InterruptedException, RemoteException;
    Entry readIfExists(Entry tmpl, Transaction txn, long timeout)
        throws UnusableEntryException, TransactionException, 
               InterruptedException, RemoteException;
    Entry take(Entry tmpl, Transaction txn, long timeout)
        throws UnusableEntryException, TransactionException, 
               InterruptedException, RemoteException;
    Entry takeIfExists(Entry tmpl, Transaction txn, long timeout)
        throws UnusableEntryException, TransactionException, 
               InterruptedException, RemoteException;
    EventRegistration
        notify(Entry tmpl, Transaction txn, RemoteEventListener listener,
               long lease, MarshalledObject handback)
        throws TransactionException, RemoteException;
    Entry snapshot(Entry e) throws RemoteException;
}

notify() では、マッチするテンプレートが write された時、 RemoteEventListener の notify(RemoteEvent theEvent) が呼ばれる。

snapshot() では、エントリのスナップショットが返される。元のエントリが 更新されても、変化しない。read(), take() のテンプレート用。

◆チュートリアルとLookupクラス

JavaSpace オブジェクトの作成方法やプログラム中での空間の入手方法は、実 行環境に依存する。

次のチュートリアルが参考になる。

[1] Qusay H. Mamoud: "Getting Started With JavaSpaces Technology: Beyond Conventional Distributed Programming Paradigms", July 12, 2005. http://java.sun.com/developer/technicalArticles/tools/JavaSpaces/

[2] The Blitz Project: "Blitz HelloWorld Example". http://www.dancres.org/bjspj/docs/docs/hello_example.html

両方とも、後者のサイトにある Lookup.java を使っている。

◆Jini

JavaSpaces は、Java 技術では、Jini (講義では後述) の一部として位置付け られている。JavaSpaces を動作させるには、まず Jini に関連するクラスライ ブラリ等をインストールする必要がある。

Jini は、以前は、"Jini Technology Starter Kit" という名前のパッケージで 配布されていた。現在は、Apache River として開発が続けられ配布されている。

JavaSpaces の例題を実行する前に、Apache River に含まれているいくつかの サーバを実行する必要がある。

■Java Spaces による Message Box の例題

◆class Message

空間に String を置くためのクラス。
   1: // From the JavaSpaces book
   2: //package jsbook.chapter1.helloWorld;
   3: 
   4: import net.jini.core.entry.Entry;
   5: 
   6: public class Message implements Entry {
   7:     public String content;
   8: 
   9:     public Message() {
  10:     }
  11: }
テンプレートでオブジェクトが null なら、ワイルドカードの意味になる。

◆HelloWriter

空間にタプルを書込むプログラム
   1: //
   2: // HelloWriter.java
   3: // 
   4: 
   5: import net.jini.space.JavaSpace;
   6: 
   7: class HelloWriter {
   8:     public static void main(String[] argv)
   9:     {
  10:         Lookup finder = new Lookup(JavaSpace.class);
  11:         JavaSpace space = (JavaSpace) finder.getService();
  12:         if( space == null )
  13:         {
  14:             System.err.println("No JavaSpace found.");
  15:             System.exit( 1 );
  16:         }
  17:         Message msg = new Message();
  18:         msg.content = "Hello" ;
  19:         try
  20:         {
  21:             space.write(msg, null, net.jini.core.lease.Lease.FOREVER);
  22:             System.out.println("HelloWriter: wrote["+msg.content+"]");
  23:         }
  24:         catch( Exception e )
  25:         {
  26:             System.err.println("JavaSpace write error "+e.getMessage());
  27:             e.printStackTrace();
  28:             System.exit( -1 );
  29:         }
  30:         System.exit( 0 );
  31:     }
  32: }
write() のインタフェース
    Lease write(Entry entry, Transaction txn, long lease)
        throws TransactionException, RemoteException;
txnはトランザクションにより複数の操作をグループ化するもの。null を 指定すれば、その機能は使われない。

lease は、時間を指定する。その時間だけは、空間がそのエントリを記憶して いる。空間が Garbage Collection に使う。Lease.FOREVER は、無限に覚えて いることを意味する。単位は、ミリ秒。

◆HelloReader

空間からタプルを読込むプログラム。タプルは、空間に残される。
   1: //
   2: // HelloReader.java
   3: // 
   4: 
   5: import net.jini.space.JavaSpace;
   6: 
   7: public class HelloReader {
   8:     public static void main(String[] argv)
   9:     {
  10:         Lookup finder = new Lookup(JavaSpace.class);
  11:         JavaSpace space = (JavaSpace) finder.getService();
  12:         if( space == null )
  13:         {
  14:             System.err.println("No JavaSpace found.");
  15:             System.exit( 1 );
  16:         }
  17: 
  18:         Message template = new Message();
  19:         Message result;
  20:         try
  21:         {
  22:             result = (Message)space.read(template, null, Long.MAX_VALUE);
  23:             System.out.println("HelloReader: read ["+result.content+"]");
  24:         }
  25:         catch( Exception e )
  26:         {
  27:             System.err.println("JavaSpace read error "+e.getMessage());
  28:             e.printStackTrace();
  29:             System.exit( -1 );
  30:         }
  31: 
  32:         System.exit( 0 );
  33:     }
  34: }
read() のインタフェース
    Entry read(Entry tmpl, Transaction txn, long timeout)
        throws UnusableEntryException, TransactionException, 
               InterruptedException, RemoteException;
read の最初の引数は、テンプレートである。 read は、空間からテンプレートとマッチするエントリを読み出す。

read() は、マッチするエントリがなければ、timeout するまで待つ。 Long.MAX_VALUE は、無限に待つことを意味する。待ちたくない時には、 JavaSpaces.NO_WAIT を使うか、raedIfExists() を使う。

◆read() におけるテンプレートとのマッチング

次の2つの条件を満たした時に、テンプレートとエントリはマッチする
  1. テンプレートの型が、エントリの型とまったく同じである、または、エ ントリの型の部分型(サブクラス)である。
  2. テンプレートの中の各フィールドが、対応するエントリ中のフィールドと マッチする。
null は、ワイルドカード(*)の意味する。 任意のオブジェクトとマッチする。Linda の ? に相当する。

例:


public class Vegetable implements Entry {
}

public class Fruit implements Entry {
}

public class Apple extends Fruit {
}

public class Orange extends Fruit {
}

「同じ値」は、serialize してバイトレベルで同じという意味。エントリは、 空間にある時にはserialize された形で保存されている。

null をワイルド・カードに使う問題点は、「本当に null の値を持つエントリ 探す」ということができないこと。 区別したい時には、Boolean を添える。


public class NotePtr implements Entry {
    public Boolean ptrIsNull;
    public Node ptr;
}

...

NodePtr template = new NodePtr();
template.ptrIsNull = new Boolean(true);
template.ptr = null; // for completeness; null by default

null をワイルドカードに使ったので、空間に置くエントリのフィールドは、 オブジェクトにする。int, boolean, float, double などは、Integer, Boolean, Float, Double などの wrappe クラスを使う。

注意:連続する read() が同じオブジェクトを返す保証はない。

◆HelloTaker

空間からタプルを読込むプログラム。タプルは、空間から取り去られる。
   1: //
   2: // HelloTaker.java
   3: // 
   4: 
   5: import net.jini.space.JavaSpace;
   6: 
   7: public class HelloTaker {
   8:     public static void main(String[] argv)
   9:     {
  10:         Lookup finder = new Lookup(JavaSpace.class);
  11:         JavaSpace space = (JavaSpace) finder.getService();
  12:         if( space == null )
  13:         {
  14:             System.err.println("No JavaSpace found.");
  15:             System.exit( 1 );
  16:         }
  17: 
  18:         Message template = new Message();
  19:         Message result;
  20:         try
  21:         {
  22:             result = (Message)space.take(template, null, Long.MAX_VALUE);
  23:             System.out.println("HelloTaker: took ["+result.content+"]");
  24:         }
  25:         catch( Exception e )
  26:         {
  27:             System.err.println("JavaSpace read error "+e.getMessage());
  28:             e.printStackTrace();
  29:             System.exit( -1 );
  30:         }
  31: 
  32:         System.exit( 0 );
  33:     }
  34: }

% diff HelloReader.java HelloTaker.java
2c2
< // HelloReader.java
---
> // HelloTaker.java
7c7
< public class HelloReader {
---
> public class HelloTaker {
22,23c22,23
<           result = (Message)space.read(template, null, Long.MAX_VALUE);
<           System.out.println("HelloReader: read ["+result.content+"]");
---
>           result = (Message)space.take(template, null, Long.MAX_VALUE);
>           System.out.println("HelloTaker: took ["+result.content+"]");
% 

take() は、read() と同じだが、エントリを空間から取り去る所が異なる。 複数の take() が重なったとしても、 エントリは1つにしか取られない。

◆Apache River のインストール

例題をコンパイルする前に、Apache River をインストールする。
  1. ダウンロード・ページ でバイナリ( apache-river-2.2.0-bin.zip か apache-river-2.2.0-bin.tar.gz ) を入手する。
  2. 入手したファイルを展開する。すると、apache-river-2.2.0 という ディレクトリができる。
    $ ls -l [←]
    total 0
    drwxr-xr-x@ 7 yas  wheel  374  6 20  2011 apache-river-2.2.0
    $ ls apache-river-2.2.0/ [←]
    LICENSE         NOTICE          configentry     lib             lib-ext
    LICENSE.txt     NOTICE.txt      examples        lib-dl
    $ []
    
  3. コンパイルには、このディレクトリの下の次のファイルが 必要になる。これを classpath で指定する。
  4. さらに、次のディレクトリにあるスクリプトや設定ファイルが必要になる。
    $ cd apache-river-2.2.0/examples/hello/ [←]
    $ ls [←]
    config  doc  index.html  krb-setup.html  lib  prebuiltkeys  scripts
    $ []
    

◆コンパイル

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/Lookup.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/Message.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/HelloWriter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/HelloReader.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/HelloTaker.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/Makefile [←]
% emacs Makefile (jinilibを、自分の環境に合わせて書き換える) [←]

% make hello [←]
javac -classpath .:/jini-core.jar:/jini-ext.jar:/reggie.jar:/outrigger.jar Lookup.java
javac -classpath .:/jini-core.jar:/jini-ext.jar:/reggie.jar:/outrigger.jar Message.java
javac -classpath .:/jini-core.jar:/jini-ext.jar:/reggie.jar:/outrigger.jar HelloWriter.java
javac -classpath .:/jini-core.jar:/jini-ext.jar:/reggie.jar:/outrigger.jar HelloReader.java
javac -classpath .:/jini-core.jar:/jini-ext.jar:/reggie.jar:/outrigger.jar HelloTaker.java
% ls  [←]
HelloReader.class       HelloWriter.class       Makefile
HelloReader.java        HelloWriter.java        Message.class
HelloTaker.class        Lookup.class            Message.java
HelloTaker.java         Lookup.java
% []

◆Jiniサーバの実行

Getting Started With River参照。

Jini関連のサーバ用にウインドウを 3 枚開く。Apache River の examples/hello に cd する。

$ cd apache-river-2.2.0/examples/hello/ [←]
$ ls [←]
config  doc  index.html  krb-setup.html  lib  prebuiltkeys  scripts
$ []
最初のウインドウでHTTP server を実行する。.sh は、Unix 用。.bat は、 Windows 用。
$ tail -1 ./scripts/httpd.sh   [←]
java -jar ../../lib/classserver.jar -port 8080 -dir lib:../../lib-dl $*
$ tail -1 ./scripts/httpd.bat [←]
java -jar ..\..\lib\classserver.jar -port 8080 -dir lib;..\..\lib-dl %1
$ ./scripts/httpd.sh  [←]
+ java -jar ../../lib/classserver.jar -port 8080 -dir lib:../../lib-dl
Jan 25, 2012 9:54:13 PM com.sun.jini.tool.ClassServer run
INFO: ClassServer started [[lib/, ../../lib-dl/], port 8080]
^Cで終了
2番目のウインドウで Service Registrar (Lookup Service) を実行する。
$ tail -4 scripts/jrmp-reggie.sh [←]
java -Djava.security.policy=config/start.policy \
     -Djava.ext.dirs=../../lib-ext/     \
     -jar ../../lib/start.jar   \
     config/start-reggie.config
$ tail -3 scripts/jrmp-reggie.bat [←]
java -Djava.security.policy=config\start.policy ^
     -jar ..\..\lib\start.jar ^
     config\start-reggie.config
$ ./scripts/jrmp-reggie.sh  [←]
+ java -Djava.security.policy=config/start.policy -Djava.ext.dirs=../../lib-ext/ -jar ../../lib/start.jar config/start-reggie.config
Jan 25, 2012 9:58:14 PM com.sun.jini.reggie.RegistrarImpl init
INFO: started Reggie: 6879dd38-7782-4f43-a172-baffff666af7, [nonsecure.hello.example.jini.sun.com], jini://example.com/
^Cで終了
3番目のウインドウで Java Space サーバを実行する。配布されているスクリ プトに .bat バグがあり、うまく実行できない。config/policy.all というファ イルは存在しない。また、.sh は、そもそも含まれていない。
$ tail -3 scripts/jrmp-outrigger-group.bat [←]
java -Djava.security.policy=config\policy.all ^
     -jar ..\..\lib\start.jar ^
     config\start-outrigger-group.config
$ ls config/policy.all [←]
ls: config/policy.all: No such file or directory
$ ls scripts/jrmp-outrigger-group.sh   [←]
ls: scripts/jrmp-outrigger-group.sh: No such file or directory
$ []
次のように手で引数を与えて実行する(Unix)。
$ java -Djava.security.policy=config/start.policy \ [←]
     -jar ../../lib/start.jar \
     config/start-outrigger-group.config
^Cで終了

◆実行

実行する前に、Jiniサーバの実行 の説明で述べたように、3つのサーバを実行しておくこと。

一方のウインドウを2枚開く。

一方のウインドウで Writer を動作させる。

$ PS1='Writer$ ' [←]
Writer$ make run-HelloWriter[←]
HelloWriter: wrote[Hello]
Writer$ wYHM_Cursor()
もう一方のウインドウで Reader や Taker を動作させる。
$ make run-HelloReader [←]
HelloReader: read [Hello]
$ make run-HelloReader [←]
HelloReader: read [Hello]
$ make run-HelloReader [←]
HelloReader: read [Hello]
$ []
$ make run-HelloTaker  [←]
HelloTaker: took [Hello]
$ make run-HelloTaker [←]
(固まる)
タプルがない状態先に take/read すると、止まる。この状態で、write すれば、 read/take は先に進む。
Writer$ make run-HelloWriter[←]
HelloWriter: wrote[Hello]
Writer$ []

◆serializationの効果

エントリが空間にある時には、serialize された形で保存されている。

read() でのマッチングは、serialize(marshaling) された形で比較される。

一度空間に入れて出すと、オブジェクトが増えることがある。


public class E1 implements Entry {
    public Integer obj1;
    public Integer obj2;
}

Integer obj = Integer(0);
E1 e = new E1();
e.obj1 =obj;
e.obj2 =obj;

引数なしのコンストラクタが必要である。

read(), take() でのエントリの復元

  1. serialize されたエントリのコピーを得る
  2. 1. から型を得る
  3. 引数なしのコンストラクタでオブジェクトを作る
  4. 1. から、public のフィールドを deserialize する。
  5. 4. の値を、オブジェクトのフィールドに代入する。
実は、Entry 全体は Serializable である必要はなかった。

read(), take() のテンプレートがループの中で不変な場合、 snapshot() を作ると効率が良くなる。

■Rinda

Linda のタプル空間 の考え方を Ruby で実現したもの。

Linda Rinda 説明
タプル() 配列[] タプル空間に置くことができるデータ構造
out write タプルをタプル空間内に生成する。
in take タプルを取り去る
rd read in/takeと似ているが、タプルがタプル空間に残る。
ライブタプル(eval命令)は、Rinda にはない。 配列の要素が nil なら、formal (ワイル ドカード, Linda の ?)の意味になる。

Rinda が提供する空間の特徴

タプルのマッチング Ruby の === 演算子
% ruby -e 'p 1 === 1' [←]
true
% ruby -e 'p Integer === 1' [←]
true
% ruby -e 'p String === 1' [←]
false
% ruby -e 'p /[abc]xy/ === "axy"' [←]
true
% ruby -e 'p /[abc]xy/ === "Axy"' [←]
false
%

■Rinda による Message Box の例題

次のようなタプルをタプル空間に置く。
["Message Box", "Hello" ]

◆make-space.rb

タプル空間を作るプログラム。
   1: #!/usr/bin/env ruby
   2: # make-space.rb -- Make a tuple space and print its URI
   3: 
   4: require 'rinda/tuplespace'
   5: 
   6: def usage()
   7:         $stderr.printf("Usage: %% %s portno\n", $0)
   8:         exit( 1 )
   9: end
  10: 
  11: def main(argv)
  12:         if( argv.length != 1 )
  13:             usage()
  14:         end
  15:         portno = argv[0]
  16:         space = Rinda::TupleSpace.new()
  17:         DRb.start_service("druby://:"+portno, space)
  18:         uri = DRb.uri()
  19:         $stdout.printf("%s\n",uri)
  20:         $stdout.printf("Type ^C to stop this program.\n")
  21:         DRb.thread.join()
  22: end
  23: 
  24: main(ARGV)
このプロセスを終了すると、タプル空間とその中のタプルは消える。

◆mbox-writer.rb

タプルを書込むプログラム。
   1: #!/usr/bin/env ruby
   2: # mbox-writer.rb -- Write a message to the message box in a tupple space.
   3: 
   4: require 'rinda/tuplespace'
   5: 
   6: def usage()
   7:         $stderr.printf("Usage: %% %s uri message\n", $0)
   8:         exit( 1 )
   9: end
  10: 
  11: def main(argv)
  12:         if( argv.length != 2 )
  13:             usage()
  14:         end
  15:         uri = argv[0]
  16:         message = argv[1]
  17: 
  18:         space = DRbObject.new_with_uri( uri )
  19: 
  20:         tuple = ["Message Box", message ]
  21:         space.write( tuple )
  22:         printf("mbox-writer: wrote[%s]\n",message)
  23: end
  24: 
  25: main(ARGV)

◆mbox-reader.rb

空間からタプルを読込むプログラム。タプルは、空間に残される。
   1: #!/usr/bin/env ruby
   2: # mbox-reader.rb -- Read a message from a message box in a tuple space.
   3: 
   4: require 'rinda/tuplespace'
   5: 
   6: def usage()
   7:         $stderr.printf("Usage: %% %s uri\n", $0)
   8:         exit( 1 )
   9: end
  10: 
  11: def main(argv)
  12:         if( argv.length != 1 )
  13:             usage()
  14:         end
  15:         uri = argv[0]
  16:         DRb.start_service()
  17:         space = DRbObject.new_with_uri( uri )
  18: 
  19:         template = ["Message Box",nil]
  20:         tuple = space.read( template )
  21:         message = tuple[1]
  22:   p tuple # for debug
  23:         printf("mbox-reader: read [%s]\n", message )
  24: end
  25: 
  26: main(ARGV)
space.read()で、タプル空間からタプルを取り出す。 最初の引数は、テンプレートである。 read() は、空間からテンプレートとマッチするエントリを読み出す。

read() は、マッチするエントリがなければ、タイムアウトするまで待つ。 第2引数に秒単位で待ち時間を指定できる。

注意:連続する read() が同じオブジェクトを返す保証はない。

◆mbox-taker.rb

空間からタプルを読込むプログラム。タプルは、空間から取り去られる。
   1: #!/usr/bin/env ruby
   2: # mbox-taker.rb -- Take a message from a message box in a tuple space.
   3: 
   4: require 'rinda/tuplespace'
   5: 
   6: def usage()
   7:         $stderr.printf("Usage: %% %s uri\n", $0)
   8:         exit( 1 )
   9: end
  10: 
  11: def main(argv)
  12:         if( argv.length != 1 )
  13:             usage()
  14:         end
  15:         uri = argv[0]
  16:         DRb.start_service()
  17:         space = DRbObject.new_with_uri( uri )
  18: 
  19:         template = ["Message Box",nil]
  20:         tuple = space.take( template )
  21:         message = tuple[1]
  22:   p tuple # for debug
  23:         printf("mbox-taker: took [%s]\n", message )
  24: end
  25: 
  26: main(ARGV)
take() は、read() と同じだが、エントリを空間から取り去る所が異なる。複 数の take() が重なったとしても、エントリは1つにしか取られない。
% diff mbox-reader.rb mbox-taker.rb [←]
2c2
< # mbox-reader.rb -- Read a message from a message box in a tuple space.
---
> # mbox-taker.rb -- Take a message from a message box in a tuple space.
20c20
<       tuple = space.read( template )
---
>       tuple = space.take( template )
23c23
<       printf("mbox-reader: read [%s]\n", message )
---
>       printf("mbox-taker: took [%s]\n", message )
% []

◆コピー

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/make-space.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/mbox-writer.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/mbox-reader.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2011/2012-01-26/ex/mbox-taker.rb [←]
% chmod +x *.rb [←]
% ls -l [←]
total 32
-rwxr-xr-x   1 yas  yas  463  2  7 23:42 make-space.rb
-rwxr-xr-x   1 yas  yas  485  2  7 23:48 mbox-reader.rb
-rwxr-xr-x   1 yas  yas  484  2  8 00:06 mbox-taker.rb
-rwxr-xr-x   1 yas  yas  406  2  7 23:40 mbox-writer.rb
% which ruby [←]
/usr/local/bin/ruby
% ruby -v [←]
ruby 1.8.6 (2008-03-03 patchlevel 114) [powerpc-darwin8.11.0]
% []
実行には、Ruby の 1.8 以降が必要。drb.rb を含んだもの。

◆実行

ウインドウを3枚開く
  1. タプル空間用
  2. writer 用
  3. reader/taker用

$ ./make-space.rb 1231 [←]
druby://ホスト名:1231
Type ^C to stop this program.
(最後に ^C で止める)
引数のポート番号(1231)は、ぶつからないような番号にする。自動的に終了し ないので、実験が終わったら ^C (Control-C) で殺す。

Writer を動作させる。

$ ./mbox-writer.rb druby://localhost:1231 hello [←]
mbox-writer: wrote[hello]
$ ./mbox-writer.rb druby://localhost:1231 hi    [←]
mbox-writer: wrote[hi]
$ []
最後のウインドウで Reader や Taker を動作させる。
$ ./mbox-reader.rb druby://localhost:1231 [←]
["Message Box", "hello"]
mbox-reader: read [hello]
$ ./mbox-reader.rb druby://localhost:1231 [←]
["Message Box", "hello"]
mbox-reader: read [hello]
$ ./mbox-reader.rb druby://localhost:1231 [←]
["Message Box", "hello"]
mbox-reader: read [hello]
$ ./mbox-taker.rb druby://localhost:1231 [←]
["Message Box", "hello"]
mbox-taker: took [hello]
$ ./mbox-taker.rb druby://localhost:1231 [←]
["Message Box", "hi"]
mbox-taker: took [hi]
$ ./mbox-taker.rb druby://localhost:1231 [←]
...
3回目の take は止まる。別ウインドウで write すれば、先に進む。

タプルがない状態先に take/read すると、止まる。この状態で、write すれ ば、read/take が終了する。

◆阿波連流

タプルは、生存期間が過ぎると、自動的にタプル空間から消える。

練習問題

この課題では、Linda、Rinda、JavaSpaces、または、類似の仕組みを利用しな さい。

★問題(601) WWWアクセスカウンタ

タプル空間の仕組みを使ってWWW のアクセス・カウンタのような動きをするプ ログラム使って作りなさい。

次の2つのプログラムを作成する。

(1) 初期化プログラム
    String url = argv[0];
    tuple = [url,0]
    space.write( tuple );

(2) アクセスされた時に動作するプログラム
    値を増やす。現在の値を画面に表示する。
なお、(2) のプログラムは、コマンドラインから実行すれば十分であり、CGI にする必要はない

★問題(602) Linda の inストリーム

Linda の inストリームを実現しなさい。 次の2つのプログラムを作成しなさい。

★問題(603) スタック

Linda の inストリーム(FIFOのキュー)を参考にして、 スタックを実現しなさい。

★問題(604) マスタ・スレーブ

マスタ・スレーブで並列処理を行うプログラムをタプル空間を使って書き 直しなさい。

★問題(605) RPC

RPC (Remote Procedure Call) を実現しなさい。複数のクライアントを区別し、 要求と応答を対応させるには、どうすればよいか考えなさい。

★問題(606) C-Linda例題の実行

このページに示された C-Linda の例題(そのままでは動作しない) を、 JavaSpaces、Rinda、または、その他のタプル空間的な機能を利用して動作させ ない。

★問題(607) タプル空間自由課題

他の課題と同程度の複雑さを持つ課題を設定し、タプル空間を用いて実現しな さい。
Last updated: 2012/02/08 18:19:31
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>