For Development HEAD DRAFTSearch (procedure/syntax/module):

12.9 control.plumbing - ポートの配管

Module: control.plumbing

このモジュールは、ポートをスレッド間のコミュニケーションチャネルとして使う ユーティリティを提供します。ユースケースによってはスレッドを必ずしも必要としませんが、 一般的なメカニズムとして、暗黙のスレッドが使われることがあります。 例えば「ポンプ」と呼ばれるデバイスは入力ポートと出力ポートをつなぎ、 入力ポートに届いたデータを自動的に出力ポートに送ります。 このデバイスは内部で入力ポートからデータを読み出すスレッドをspawnします。

このモジュールを使って、1つ以上のポートからデータを吸い出し、それを1つ以上のポートへと 送り届けるシステムを作ることができます。 あたかも注水口(inlet)と排水口(outlet)がある配管(plumbing)システムを作るような ものなので、このシステムをplumbingと呼びます。

plumbingの完全な仕様については下のmake-plumbingの項を参照してください。

配管システム

Function: make-plumbing

{control.plumbing} 新たなplumbingを作って返します。

機能させるためには、plumbingは1つ以上のinlet (注水口) と 1つ以上のoutlet (排水口)を持つ必要があります。 データはinletから流れ込み、outletから流れ出してゆきます。

inlet, outletそれぞれどちらにも、入力ポート、出力ポートのいずれもつなぐことができます。 すなわち、次の4つの組み合わせがあり得ます。

Inlet入力ポート

生産者から送られるデータを読み出せる入力ポートです。 このポートが繋がれた場合、専用のスレッドが作られポートからデータを読み出します。

Inlet出力ポート

このポートに書き込まれたデータが配管システムに流れてゆきます。

Outlet入力ポート

配管を流れたデータが送られて、消費者がそこから流されたデータを読み出すことができる 入力ポートです。内部に、送られたデータを読み出されるまで保持しておくバッファを持っています。

Outlet出力ポート

配管を流れたデータが送られる出力ポートです。

読み書きの動作は排他制御されているので、plumbingをスレッド間のコミュニケーションチャネル として使うことができます。

下の例は、サブプロセスのstdoutとstderrを現在の出力ポートにつなぐ例です。 サブプロセスからの両出力は、自動的にパイプから読み出されて逐次 現在の出力ポートへと送られます。

(use gauche.process)
(use control.plumbing)

(let ([p (run-process '(ping "127.0.0.1") :output :pipe :error :pipe)]
      [plumbing (make-plumbing)])
  (add-inlet-input-port! plumbing (process-output p))
  (add-inlet-input-port! plumbing (process-error p))
  (add-outlet-output-port! plumbing (current-output-port)))

典型的な配管構成については、ユーティリティ手続きが定義されていて いちいちポートをひとつづつ繋がなくても良いようになっています。 下の “組み立て済みplumbing” の項を参照してください。

Function: plumbing? obj

{control.plumbing} objがplumbingであれば#tを、そうでなければ#fを返します。

Function: plumbing-inlet-ports plumbing
Function: plumbing-outlet-ports plumbing

{control.plumbing} それぞれ、plumbingのinletおよびoutletに接続されたポートのリストを返します。

Function: plumbing-get-port plumbing name

{control.plumbing} name引数はシンボルでなければなりません。 plumbingに接続されたポートで、名前nameを持つものを返します。 該当するポートが無ければ#fが返ります。

Function: port-plumbing port

{control.plumbing} open-outlet-input-portopen-inlet-output-port、あるいは それらを使うユーティリティ手続きによって、plumbingのために作られたポートは、 そのプロパティとして元になったplumbingへの参照を保持しています。 この手続きはportが保持しているplumbingを返します。 portがplumbingによって作られたものでない場合は#fが返されます。

既にあるポートをadd-inlet-output-port!等でplumbingに接続した場合は、 そのポートはplumbingによって作られたわけではないので、この手続きは#fを返す ことに注意してください。

plumbingにポートを作ったり接続したりする

Function: open-inlet-output-port plumbing :optional name

{control.plumbing} plumbingのinletに接続された出力ポートを作って返します。 このポートに書き込まれたデータは、plumbingを通ってそのoutletに流れてゆきます。

省略可能なname引数はシンボルか#fでなければなりません。 これがシンボルの場合、作られたポートはplumbing-get-portplumbingから取り出すことができます。

また、返されたポートにport-plumbingを適用するとplumbingが得られます。

返されたポートをクローズすると、それはplumbingから取り除かれます。 全てのinletポートがクローズされた時に、plumbingのoutletポートはEOFを受け取ります。

Function: add-inlet-input-port! plumbing iport :optional name

{control.plumbing} 既存の入力ポートiportplumbingのinletに接続します。 iportからデータを読み出し、それをplumbingのoutletに送るための スレッドが作られます。

省略可能なname引数はシンボルか#fでなければなりません。 これがシンボルの場合、作られたポートはplumbing-get-portplumbingから取り出すことができます。

iportからEOFが読まれたら、そのポートはplumbingから取り除かれます。 全てのinletポートがクローズされた時に、plumbingのoutletポートはEOFを受け取ります。

Function: open-outlet-input-port plumbing :optional name

{control.plumbing} plumbingのoutletに接続された入力ポートを作って返します。 plumbingのinletに流れ込んだデータをそのポートから読み出すことができます。

省略可能なname引数はシンボルか#fでなければなりません。 これがシンボルの場合、作られたポートはplumbing-get-portplumbingから取り出すことができます。

返されたポートにport-plumbingを適用するとplumbingが得られます。

全てのinletがクローズされると、返されたポートからEOFが読まれます。 返されたポートはいつでもクローズできます。クローズされるとそれは plumbingのoutletから取り除かれ、データは送られなくなります。

Function: add-outlet-output-port! plumbing oport :optional name :key close-on-eof asynchronous

{control.plumbing} 既存の出力ポートoportplumbingのoutletに接続します。 plumbingに流れ込んだデータはその都度oportに送られます。

省略可能なname引数はシンボルか#fでなければなりません。 これがシンボルの場合、作られたポートはplumbing-get-portplumbingから取り出すことができます。

返されたポートはいつでもクローズできます。クローズされるとそれは plumbingのoutletから取り除かれ、データは送られなくなります。

デフォルトでは、全てのinletがクローズされた後でもoportはオープンしたままにされます。 plumbingがそれ以降そこに書き込むことはありませんが、 oportに追加で出力することはできます。 ただし、close-on-eofキーワード引数に真の値を指定すると、 plumbingは全てのinletがクローズされた時点でoportをクローズします。 これは、例えばファイルを出力用にオープンしてそのポートをoportとして 渡し、データが来なくなったら自動的にクローズしたい、という場合に便利です。 けれども、例えばログ出力を監視するためにoportに標準出力を渡すといった場合は、 勝手にクローズされると困るでしょう。

asynchronousキーワード引数に真の値が渡された場合、 oportにデータを送るための専用スレッドが作られます。 これは、oportがOSのパイプやソケットのようにブロックする可能性がある場合に 便利です。asynchronousが無い場合、oportがブロックすると plumbing全体がブロックしてしまう可能性があります。 asynchronousを指定しておくと、このポートへの出力以外は動きつづけます。

Function: plumbing spec …

{control.plumbing} plumbingを簡単に組み立てるためのユーティリティです。 新しいplumbingを作り、specに従ってinletとoutletを接続して、 作られたplumbingを返します。各specは以下に挙げる形式のいずれかです。 説明中、iportは入力ポート、oportは出力ポート、 nameはシンボルです。

(< iport)
(< iport name)

iportをplumbingのinletに接続します。名前nameをつけることもできます。

(< name)

名前nameを持つinlet出力ポートを作ります。 作られた出力ポートはplumbing-get-portで取り出すことができます。

(> oport)
(> oport name)
(> oport (option …))
(> oport name (option …))

oportをplumbingのouletに接続します。nameが与えられればそれが 名前になります。また、optionとしてキーワード :close-on-eof:coe:asynchronous:asyncの 組み合わせを指定することができます。:close-on-eof:asynchronousadd-outlet-output-port!で同名のキーワード引数に真の値を指定するのと同じ効果 を持ちます。:coe:asyncはそれぞれ:close-on-eof:asynchronousの省略形です。

(> name)

名前nameを持つoutlet入力ポートを作ります。 作られたポートはplumbing-get-portで取り出せます。

組み立て済みplumbing

以下に挙げるのは、典型的な組み立てのplumbingを作る便利手続きです。

Function: make-pipe :key num-inlets num-outlets

{control.plumbing} パイプはinlet出力ポートとoutlet入力ポートを持つ受動的なplumbingです。 inlet出力ポートに書き込まれたデータは各outlet入力ポートにバッファされ、 読み出されるのを待ちます。 この手続きは、num-inlets個のinlet出力ポートと、 num-outlets個のoutlet出力ポートを持つパイプを作り、 inletポートのリストとoutletポートのリストの2つの値を返します。

num-inletsnum-outletsはともに正の正確な整数でなければなりません。 省略された場合は1とみなされます。

outletが複数ある場合、データは複製されます。つまり、それぞれの消費者は 同じデータを読み出します。それぞれのoutletが独立したバッファを持つので、 各消費者はそれぞれのペースで読み出すことができます。

作られたplumbingを得るには、返されたポートのいずれかにport-plumbingを 適用します。

この手続きはsys-pipeと似ていますが、 sys-pipeはOSのパイプを使い、その両端に繋がれた入力ポートと出力ポートを 返します (他のファイル操作参照)。 make-pipeが作るパイプはあくまでユーザ空間の構築物で、 プロセス間通信には使えず、スレッド間通信にしか使えません。 一方、make-pipeはn-入力/m-出力のパイプを作ることができます。

Function: make-pump inlet-iports outlet-oports

{control.plumbing} ポンプは与えられた入力ポートからデータを読み出し、それを 与えられた出力ポートへと送り出す、アクティブなplumbingです。

inlet-iports引数は入力ポートのリスト、 outlet-oports引数は出力ポートのリストでなければなりません。

各inlet入力ポートそれぞれに対し、そこから読み出すスレッドがスポーンされます。

Function: open-broadcast-output-port oport …

{control.plumbing} 引数は全て出力ポートでなければなりません。 新たな出力ポートを返します。そのポートに書かれたデータは、全てのoportへと送られます。 内部的には、この手続きはplumbingを作り、oport …をoutletに接続し、 そしてinlet出力ポートを返します。返されたポートにport-plumbingを適用すれば plumbingが得られます。

これはCommon Lispのmake-broadcast-streamにあたります。

Function: open-tapping-input-port inlet-iport outlet-oport :key close-on-eof

{control.plumbing} これは、inlet-portからデータを吸出しoutlet-oportに送り出すポンプを 作りますが、同時にそこに流れるデータを読み出せるoutlet入力ポートを作って返します。

例えばそれは、プロセスの出力を監視するのに使えます。 標準出力をパイプに流す子プロセスを作り、 (open-tapping-input-port (process-output subprocess) (current-output-port)) を呼びます。 すると、子プロセスの出力は自動的に現在の出力ポートに流されますが、 そのデータは戻り値の入力ポートから読み出せるので、そこで監視することができます。

inlet-iportから読み出されoutlet-oportに送られるデータは、 戻り値の入力ポートからも読み出せるようにバッファされることに注意してください。 監視する必要がなくなったらいつでも、戻り値の入力ポートを閉じることができます。 そうすれば流れるデータはバッファされなくなりますが、ポンプはinletからのデータが EOFに達するまで動きつづけます。

ポンプのplumbingは、戻り値の入力ポートにport-plumbingを適用すると得られます。



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT