JanGaJan.com

Is fun? JOY!

UnixのパイプをRubyで扱う

積読消化シリーズです。
なるほどUnixプロセス ― Rubyで学ぶUnixの基礎
UnixでのパイプをRubyで表すとどうなるか。

Rubyで扱うパイプ

パイプとは

パイプとは、「単方向のデータの流れ」です。
パイプでは、2つのプロセス(A,B)で、ストリームを介して、一方通行に通信します。
(ストリームは最後の方に補足します。データを溜めておく場所のイメージ)

単一プロセスでのパイプ

一つのプロセスでの、パイプを表現すると以下の通りです。

1
2
3
4
5
6
7
8
p IO.pipe #=> [#<IO:fd 7>, #<IO:fd 8>]
reader, writer = IO.pipe
# reader => #<IO:fd 7>
# writer => #<IO:fd 8>

writer.write("書き込みました。")
writer.close
puts reader.read # =>書き込みました。

reader(A)、writer(B)とした場合、B => Aというデータの流れは可能ですが、A => Bはできません。
writer.writeでストリームに情報を書き込みます。
reader.readでストリームに書き込まれた情報を読み込みます。

メモ

  • reader.readの前に、writer.closeをする
    • reader.readは区切り文字が見つかるまでストリームを読み込み続けます
    • writer.closeすることで、ストリームへの書き込みが終了させ、区切り文字を設定します
  • writer.closeで閉じたストリームを更新することはできない

親子プロセスでのパイプ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
reader, writer = IO.pipe
# reader => #<IO:fd 7>
# writer => #<IO:fd 8>

# forkにより子プロセスを生成
fork do
  reader.close
  10.times do
    writer.write "Another one bite the dust\n"
  end
end

writer.close
print reader.read
# => Another one bite the dust
# Another one bite the dust
# Another one bite the dust
# Another one bite the dust
# Another one bite the dust
# Another one bite the dust
# Another one bite the dust
# Another one bite the dust
# Another one bite the dust
# Another one bite the dust

親プロセス、子プロセスでは、それぞれ使用しないIOインスタンスをcloseしています。
これは、子プロセスが親のファイルディクリプタを複製して利用するためです。
ファイルディスクリプタというのは、ストリームの出入り口をイメージすると分かりやすいかもしれません。
writerやreaderでwrite/readするためのデータの取り出し口ですね。
* 親プロセスでは、write処理は行わないため、writer.closeをします。
* forkした子プロセスでは、read処理は行わないため、reader.closeをします。
これで、利用しないファイルディクリプタに影響を与えなくなります。

親子プロセス両方でreader.close

forkした子プロセスがwriteする前に、親プロセスでreader.closeをすると、どうなるでしょうか。

1
2
3
4
5
6
7
8
9
10
11
12
13
reader, writer = IO.pipe

fork do
  reader.close
  10.times do
    writer.write "Another one bite the dust\n"
    # => `write': Broken pipe (Errno::EPIPE)が発生
  end
end

reader.close # この行を追加
writer.close
print reader.read

親子でreader.closeをすると、書き込んだ情報の出力先がなくなります。
そのため、子プロセスで書き込みをするとエラーが発生します。

補足:ストリーム

スラッシュドットの以下の記事がイメージしやすかったです。 gatekeeperの日記: TCPとUDPの違い

ストリーム型というのは1個のデーターをためておく場所(ストリーム)があって、 ストリームの性質としては最初に入れたデーターが最初に出てくる。(要はFIFOだな) … それがどういうことになるかというと、ストリーム型では何回かに分けて書き込んだデーターを一気に読み込んだり、 一気に読み込んだり、 逆に一度に書き込んだデーターを細切れで読み出したりできる。 つまり、読み書きの境界はストリームに入った時点で保存されない。

ストリームはデータを溜めておく場所で、明示的に終了したよって宣言しないと、データの区切りが分からないものなんですね。
closeすることで、データの境界を設定します。
それまでは好きなだけwriteでデータを溜め込みます。
しかし、readするためには、closeでデータの区切りを宣言しないといけません。
宣言しない限りreadでのデータの読み込み処理は完了しないんですね。

Comments