Newsqueak (2): チャネル

昨日に引き続き、Newsqueak話。プロセス間通信に使われるチャネル(chan)について。

チャネルこそがこの言語の胆である。Newsqueakではプロセス間通信に共有メモリではなく、メッセージパッシングモデルを採用している。プロセスはチャネルと呼ばれるバッファリングなしの通信路を介してメッセージをやりとりする。バッファリングをしないということは、受信側の準備ができるまで送信しないと言うことであり、自然と通信は同期されることになる。

Rob Pikeは「Channel as capabilities」と言っているが、見方を変えるとチャネルはファイル記述子やケイパビリティと見なすことができる。チャネルを取得できれば、その先にどんなファイルなりサービスがつながっていようとその内容を読み書きできる。オリジナルのCSPやSqueakと異なり、チャネルはファーストクラスオブジェクトに格上げされたので、チャネルを変数に代入したり、関数(プロセス)の引数として渡すことが出来る。これによってチャネルの柔軟性が大きく向上している。

チャネルの作成は、

  c:=mk(chan of int);

となる。mk()はオブジェクトのコンストラクタである。チャネルは型付きの通信路を提供し、上の場合はint型のデータの授受に使われることを意味する。

チャネルからの受信は"<-c"、送信は"c<-"と書く。矢印を前置するか後置するかで意味が変わる。例えば、チャネルにint値を書き込むと、書き込んだプロセスは、通信相手のプロセスがチャネルからint値を読み込むまでブロックされる。逆に言うと、チャネルを読んでいるプロセスは、書き込みがあるまでブロックされる。

具体例をカウンタプロセスで示めそう。prog counterへの引数としてチャネルcを渡し、beginでプロセスを起動する。この時点でメインプロセスとcounterプロセス間がチャネルでつながった状態になる。counterは無限ループなので、制御が呼び出し元のメインプロセスに戻らないのでは?と思うかもしれないが、チャネルcにiの値を書き込んだ時点で制御がメインプロセスに戻る。そして、"<-c"とチャネルcからint値を受信するたびに、counterとの間で制御が遷移する。print()の出力は1づつインクリメントしていく。この挙動はコルーチンを想像すれば、わかりやすいと思う。

counter:=prog(c:chan of int)
{
        i:=0;
        for (;;) {
                c<-=i++;
        }
}

c:=mk(chan of int);
begin counter(c);

print(<-c);
0
print(<-c);
1
print(<-c);
2

このように必要に応じてプロセスを生成し、チャネルで通信しながら処理を行うのが、Newsqueakのプログラミングスタイルになる。

ちなみにチャネルは非同期通信をサポートしていないが、beginと組み合わせて擬似的に実現できる。まずbeginで始まったプロセスを、pthreadのようにjoinする仕組みはない。そこでprogの引数にチャネルと送信する値を渡してやればよい。つまり、

begin prog(c: chan of int, a: int) {
    c <-= a;
} (ch, v);

とやれば、チャネルchに値vを書き込む非同期手続きになる。実際に利用する場合はこれを次のようにラップしてやればよい。

async_send := prog(ch: chan fo int, v: int) {
    begin prog(c: chan of int, a: int) {
        c <-= a;
    } (ch, v);
};

async_send(c, 23);