HCl 1.6 の拡張機能

HCl 1.6では以前から要望のあったco-routineを実現するための疑似マルチプロセス 機能およびネットワークインターフェースプリミテイブを実装している。

Pseudo multi-process

HCl では新しいbuilt-in オブジェクトとしてstackを導入した。これにより HCl 起動時のデフォルトスタック空間とは別にユーザーが独立した スタック空間を宣言する事ができる。Stackを操作するための関数として次の ものがユーザーに解放されている。
hcl:make-stack &key name
新しいスタックをアロケートする。値はStackの構造体である。アロケートされた スタックは初期状態として非アクテイブ状態であり、hcl:make-processにより 活性化される。活性化されているstack構造体をプロセスと呼ぶ事にする。
	[1]->(setq s1 (hcl:make-stack :name 'my-stack))
	#<stack :name my-stack :not-active :cstk 512 :ostk 512>
	
hcl:make-process stack function list-of-args
stack上で関数functionを引き数リストlist-of-argsに対して 作用させる。 functionが終了した場合はその値を返す。functionがhcl:resume で実行が中断された場合は値は不定である。make-processを実行した プロセスが新に作成されるプロセスの親プロセスとなる。
	[2]->(defun foo (x)
	         (loop (print x)
	               (incf x)
	               (hcl:resume)))
	foo
	[3]->(hcl:make-process s1 #'foo '(0))
	0
	#<stack :name my-stack :not-active :cstk 512 :ostk 512>
	
hcl:make-new-process function list-of-args
新しいスタックをアロケートし、その上でfunctionlist-of-args に対して作用させる。値は作成されたプロセスである。 hcl:make-processと似ているがあらかじめstackオブジェクトを作る必要が ない点が異なる。
hcl:resume &optional process
process上で中断されている関数の評価を再開(resume)する。process が与えられなかった 場合、自分の親プロセスの評価を再開する。これはdetachと同じ意味になる。
	[4]->(hcl:resume s1)
	1
	#<stack :name my-stack :not-active :cstk 512 :ostk 512>
	[5]->
	2
	#<stack :name my-stack :not-active :cstk 512 :ostk 512>
	
hcl::stack-parent stack
hcl::stack-parentはstackの親プロセスを値とする。親プロセスが nilである場合はそのスタック空間はアクテイブでない、つまりresumeが できないことを示している。

プロセス間通信機構

異なるスタック空間で実行されているco-routineが通信するメカニズムとして HCl 1.6では 動的共有変数(Dynamic shared variable) をサポートしている。

動的共有変数とはプログラムのコンテクストとしては通常の変数であるが 自分自身を含む他のスタック空間上の動的共有変数とメモリ上の同一アドレス を共有しているものである。共有変数は参照、代入のコストが通常の変数と 同等であり、プログラム中の意味も通常のCommon Lispの変数と全く同じである。 似た概念にstatic closure中に閉じこめられた 変数があるが、これは共有関係を関数定義時に宣言する必要がある。 これに対しHCl の動的共有変数は関数の実行中であっても共有関係を変更 できるものであり、自由度が高い。

HCl では以下のようなマクロ、関数を用意して動的共有変数をサポートする。

hcl:with-shared-variable
hcl:with-shared-variableは動的共有変数を宣言するマクロであり、次の 形式で使用される。 Syntax
	(hcl:with-shared-variable
	      ({var|(var init)|(var :process #'fn)}*)
	      {(:equivalence {var}*)}*
	       . body)
	
簡単な例でwith-shared-variableマクロを説明する。最も簡単な例は以下の ようなものである。この例ではIN1,IN2,Qが共有変数として宣言されている。 関数NANDはIN1,IN2がともに1の場合にQ=0,それ以外でQ=1となる関数である。 Example
	(defun NAND ()
	    (let (internal-q)
	         (hcl:with-shared-variable (IN1 IN2 (Q 0))
	              (loop (hcl:resume)
	                    (setq internal-Q 
	                          (if (and (= IN1 1)(= IN2 1))
	                              0
	                              1))
	                    (hcl:resume)
	                    (setq Q internal-Q)  ;; update Q
	                    ))))
	
この例では素子としてNANDを定義したに過ぎない。これを用いてRS-LATCHを 定義する場合、NANDの入出力を結線する必要がある。関数RS-LATCHは内部に 2個のNANDをプロセスとして含み、それがRS-LATCHを構成するように変数が 共有される。
	(defun RS-LATCH ()
	   (hcl:with-shared-variable (IN1 IN2 (Q1 0) (Q2 1)
	                              (NAND1 :process #'NAND)
	                              (NAND2 :process #'NAND))
	        (:equivalence IN1 (NAND1 . IN1))
	        (:equivalence IN2 (NAND2 . IN1))
	        (:equivalence Q2 (NAND1 . IN2) (NAND2 . Q))
	        (:equivalence Q1 (NAND2 . IN1) (NAND1 . Q))
	        (loop (hcl:resume)
	              (hcl:resume NAND1)
	              (hcl:resume NAND2)
	              (hcl:resume)
	              (hcl:resume NAND1)
	              (hcl:resume NAND2))))
	
ここでNAND1,NAND2は共有変数であるが、その値はプロセスである。この場合は RS-LATCHを構成するNANDが代入されている。:equivalenceで始まるformは 共有関係の宣言である。この宣言においてはそのwith-shared-variableマクロ で宣言された共有変数はsymbolがそのまま、サブプロセス中の変数は(プロセス変数 . var) の形式で指定される。プロセスがさらにネストされている場合は(process subprocess subsubprocess ... . v の形式で指定できる。同時に複数の変数の共有関係が宣言できる。
hcl:with-shared-variableマクロで初期化されたプロセスに対して HCl 1.6ではプロセス内の変数をアクセスするメカニズムをいくつか 用意している。
hcl:send-message process message &optional arg1 arg2
hcl:send-messageはプロセス中のアクテイブな変数を操作するための低レベル プロトコルである。messageとしては以下のものが使用できる。
hcl:value
arg1として共有変数名であるシンボルを取る。値はプロセス内のarg1の共有 変数の現在の値である。
[9]->(setq rs-ff (hcl:make-new-process #'RS-LATCH ()))
#<stack :name rs-latch :parent nil :cstk 512 :ostk 512>
[10]->(hcl:send-message rs-ff 'hcl:value 'Q0)
0
[11]->(hcl:send-message rs-ff 'hcl:value 'Q1)
1
	
hcl:set-value
hcl:valueメッセージに対応する更新メッセージ。arg2が新しい値である。
[12]->(hcl:send-message rs-ff 'hcl:set-value 'Q0 100)
100
[13]->(hcl:send-message rs-ff 'hcl:value 'Q0)
100
	
hcl:variable-list
そのプロセス中で宣言されている共有変数のリストを返す。
[14]->(hcl:send-message rs-ff 'hcl:variable-list)
(Q1 Q0 IN2 IN1)
	
hcl:process-list
そのプロセス中で宣言されているサブプロセスが代入されている変数名 のリストを返す。
[15]->(hcl:send-message rs-ff 'hcl:process-list)
(NAND1 NAND2)
	
ネストされているサブプロセスに対してメッセージを送る場合は メッセージを(sub-process-name . message)の形式で記述する。 さらにネストが深い場合は(sub-process-name sub-sub-process-name . message) の様に指定できる。 ネストされているサブプロセスそのものを取り出す場合はサブプロセスが バインドされている変数名の値をhcl:valueメッセージで取り出せばよい。 さらにそのサブプロセスで実際に動いているトップレベル関数名を知りたい 場合は (hcl::stack-name process )で知ることができる。なお、 hcl::stack-nameでプロセス内トップレベル関数を知れるのは hcl:with-shared-variableマクロでプロセスが初期化された場合のみである。 hcl:make-processで初期化する場合はマニュアルで (setf (hcl:stack-name stack ) name) にのりstack構造体のnameスロットをセットしなければいけない。
[16]->(hcl:send-message rs-ff '(NAND1 . hcl:value) 'Q)
100
[17]->(hcl:stack-name rs-ff)
rs-latch
	
hcl:process-variable process variable-name
hcl:process-variableはprocess内で共有変数として宣言されている variable-nameの変数 の現在の値を返す。process内のサブプロセス中の変数の場合は (hcl:process-variable stack '(process-name . var))のように指定する。
(setf (hcl:process-variable stack variable) new-value)も可能である。
[18]->(hcl:process-variable rs-ff 'q0)
100
[19]->(setf (hcl:process-variable rs-ff 'q0) 0)
0
[20]->(hcl:process-variable rs-ff 'q0)
0
	
hcl:equivalence process1 var1 process2 var2
process1中のvar1process2中のvar2を結合する。 process1, process2は同一であっても良い。 hcl:with-shared-variable マクロ中の:equivalenceによる共有は宣言的に記述されているが、 hcl:equivalence関数による共有宣言はプロセスが実行中(アクティブ)に動的 に行える点が異なる。 var1,var2はいずれもhcl:with-shared-variableで共有変数として宣言されて いなければいけない。またprocess1,process2はhcl:equivalenceが実行される 前に活性化されていなければいけない。
[18]->(setq NAND3 (hcl:make-new-process #'NAND ()))
#<stack :name nand :parent t :cstk 512 :ostk 512>
[19]->(hcl:equivalence nand3 'in1 rs-ff 'q0)
0
[20]->(setf (hcl:process-value rs-ff 'q0) 10)
10
[21]->(hcl:process-value nand3 'in1)
10
	
hcl:unlink-variable process var
hcl:unlink-variableはprocess中のvarを現在の共有関係から切り放す。変数の 値は保存されているが、その変数を共有している他の変数はなくなる。
[22]->(hcl:unlink-variable nand3 'in1)
10
[23]->(setf (hcl:process-value rs-ff 'q0) 0)
0
[24]->(hcl:process-value nand3 'in1)
10
	

TCP/IPインターフェース

HCl 1.6ではいくつかのstream-io,block-ioの2通りのTCP/IPインターフェースプリミテイブを 提供している。
si:open-tcp-stream host-name port &optional (errorp t)
open-tcp-streamはhost-nameで示されるリモートホストとポート番号portで TCP/IPストリームを確立し、それを値とする。 host-nameが/etc/hostsに登録されているホスト 名、あるいは"133.50.33.12"のようなinet形式のIPアドレスであればそのホスト に対しポート番号portで接続要求(client)を出す。その時host-nameのマシン 上でportを聞いているプロセスが無い場合にはエラーとなる。errorpがnilで あればエラーとならずにnilを返す。
host-nameがnilであった場合はポート番号portでサーバーモードで他のプロセス からの接続要求を待つ。接続が完了した場合、si:open-tcp-streamはTCP-IP ソケットに結合された双方向ストリームオブジェクトを値とする。(*) そのstreamに対して全ての入出力関数を実行可能である。
si:read-block stream array start size
si:read-blockはstreamからsizeバイトのバイナリデータを読み取り、それを (simple-array * :element-type (or string-char (unsigned-byte 8))) であるstringにオフセットstartからsizeバイト分代入する。 値は実際に読みとったバイト数である。
si:write-block stream array start size
si:write-blockはstreamに(simple-array * :element-type (or string-char (unsigned-byte 8)) であるstringのオフセットstartからsizeバイト分をstream に書き込む。値は実際に書き込んだバイト数である。 以下の例はネットワーク接続された2台のマシン(towns, ss1)を結ぶTCP/IP ストリームを作成し、S式を交換する手順である。
	ss1 (server)                                     | towns (client)
	---------------------------------------------------------------------------------------------
	[1]->(system "hostname")                      |[1]->(system "hostname")
	ss1                                              |towns
	0                                                |0
	[2]->(setq tcp (si:open-tcp-stream nil 5010)) |[2]->(setq tcp (si:open-tcp-stream "ss1" 5010)
	#<stream #<pointer 0x.> #<pointer 0x.>>          |#<stream #<pointer 0x.> #<pointer 0x.>>
	[3]->(print '(1 2 3) tcp)                        |[3]->(read tcp)
	(1 2 3)                                          |
	[4]->(force-output tcp)                          |
	t                                                |(1 2 3)
	[5]->(read tcp)                                  |[5]->(print 12345 tcp)
	                                                 |12345
	                                                 |[6]->(force-output tcp)
	12345                                            |t
	[6]->(close tcp)                                 |[7]->(close tcp)
	---------------------------------------------------------------------------------------------
	
si::get-input-file-descriptor stream
streamがファイルあるいはTCP/IPソケットに結合された入力モードストリーム である場合、そのunix file descriptor(integer)を返す。
[1]->(si:get-input-file-descriptor *standard-input*)
0
[2]->
	
si::get-outpput-file-descriptor stream
streamがファイルあるいはTCP/IPソケットに結合された出力モードストリーム である場合、そのunix file descriptor(integer)を返す。
[1]->(si:get-output-file-descriptor *standard-output*)
0
[2]->
	
si::select-input mask time-out
si:select-input(*) はunixのselectのreadfdsに対応する機能を提供する。 maskはfixnumであり、setされているビットに 対応するfile descriptorに直ちに読み取れるデータがあるかどうかをチェック する。値はfixnumであり、データがreadyであるbitが1にセットされたfixnum が返される。指定された全てのfdがreadyで無い場合にはnilが返される。 si::select-inputは指定されたfile descriptorが全てreadyでない場合に ブロックされる。ブロックされる時間はtime-out(fixnum)により秒単位で 設定される。
[1]->(setq fd (si:get-output-file-descriptor *standard-output*))
0
[2]->(si:select-input (ash 1 fd) 10)
1
[3]->(read-char *standard-input*)
\Newline
[4]->
	
si:select-output mask time-out
si:select-outputはunixのselectのwritefdsに対応する機能を提供する。 maskはfixnumであり、setされているビットに 対応するfile descriptorが書き込み可能な状態にあるかどうかをチェック する。値はfixnumであり、fdがreadyであるbitが1にセットされたfixnum が返される。指定された全てのfdがreadyで無い場合にはnilが返される。 si:select-outputは指定されたfile descriptorが全てreadyでない場合に ブロックされる。ブロックされる時間はtime-out(fixnum)により秒単位で 設定される。
si:select-exception mask time-out
si:select-exceptionはunixのselectのexceptfdsに対応する機能を提供する。 maskはfixnumであり、setされているビットに 対応するfile descriptorにexceptional conditionが発生しているかどうか をチェックする。値はfixnumであり、exceptional conditionが発生して いるbitが1にセットされる。全てのfdにexceptionが発生していなければ nilを返す。 si::select-exceptionsは指定されたfile descriptorが全てexceptional condition でない場合にブロックされる。ブロックされる時間はtime-out(fixnum)により秒単位で 設定される。

(*)サーバー モードのsi:open-tcp-streamではタイムアウトの制御が無いため他のプロセスが 接続要求するまで無限にプロセスをブロックしてしまう。
(*) HCl ver1.5以前の版ではsi:wait-for-inputsとして 定義されていた
もくじ