「名前付きパイプ」(FIFO/Named Pipe)を使ってプロセス間通信を試してみる

Bash, CentOS, Linux, Ubuntu

はじめに

Bashのストリーム/パイプ処理を視覚的に理解する のエントリで触れましたが、「名前付きパイプ」というものがあって今のプロジェクトで使う可能性が出てきたので取り上げてみました。

本稼働するシステムで使うことは初めてになりそうなので、楽しみです。

検証環境

$ uname -moi
x86_64 MacBookPro11,4 Darwin
$ bash -version
GNU bash, バージョン 5.0.2(1)-release (x86_64-apple-darwin18.2.0)

通常のパイプ

「通常のパイプ」は「無名」です。
使用しているプロセスが動作中の間だけ存在します
プロセスが終了するとともに消えてしまいます。

パイプを挟んで前後にプロセスが1つずつ存在します。
一方は「パイプ」へ出力、他方は「パイプ」からの入力を待ちます。

(例)

# `seq` で 1から10 の数値を出力し、 `tail` で入力を受け付ける。
$ seq 1 10 | tail -f

名前付きパイプ

  • 「名前付きパイプ」 は「ファイル名」を持ちます。
  • プロセスが消滅しても存在し続けます。(そのため、使わなくなったら削除する必要があります。)
  • プロセス間通信 (IPC) を行うためにプロセスがオープンして使用します。

試してみる

簡単な例を上げます。

名前付きパイプの作成

mkfifo というコマンドを実行すると、 「名前付きパイプ」 ファイルが作成できます。
ここでは Named Pipe の頭文字をとって np としています。

$ mkfifo np

ls -l コマンドでファイルができたか確認すると、
ファイルタイプ(一番左の文字)を見ると、 p となっていますが、このファイルが名前付きパイプであることを示しています。

$ ls -l np
prw-rw---- 1 genzouw wheel      0  3 18 08:00 np

名前付きパイプへのInput/Output

ここでターミナルを2つ開きます。

一方のターミナルで以下のコマンドを実行します。

$ echo hello > np

実行してもコマンドが終了しません。
この状態でもう一方のターミナルで以下のコマンドを実行します。

$ cat np
hello

cat を実行したターミナルのプロセスは即座に終了しましたが、はじめに echo したプロセスも同時に終了します。
「名前付きパイプ」 は、パイプに対して出力するプロセス、入力するプロセスをペアリングし、両者の出力・入力を紐付けるための仕組みを提供します。

「名前付きパイプ」に対して複数プロセスから書き込みしても大丈夫なの?欠損とか起きない?

「ファイル」という姿をしているため、複数プロセスから書き込みした場合に欠損など起きるのではないかという点を懸念したので、試してみます。

ターミナルを4枚開き、以下のコマンドを同時に実行することで、4プロセスから名前付きパイプに書き込みを行います。

# ターミナルその1
$ for ((i=0; i<10000; i++)); do
 echo "ps1" >> np
done
# ターミナルその2
$ for ((i=0; i<10000; i++)); do
 echo "ps2" >> np
done
# ターミナルその3
$ for ((i=0; i<10000; i++)); do
 echo "ps3" >> np
done
# ターミナルその4
$ for ((i=0; i<10000; i++)); do
 echo "ps4" >> np
done

更にターミナルを1枚開き、以下のコマンドを実行してデータを読み取ります。
( ファイルを読み込み、コンソール出力させながら out.txt にも書き出しておきます。 )

$ tail -f np | tee -a out.txt

数秒後、合計5つのプロセスは正常に終了しました。
out.txt の内容を確認し、ファイルの内容に欠損が無いか確認してみます。

$ cat out.txt | wc -l
40000
$ grep ps1 out.txt | wc -l
10000
$ grep ps2 out.txt | wc -l
10000
$ grep ps3 out.txt | wc -l
10000
$ grep ps4 out.txt | wc -l
10000

重複、欠損はなさそうですね。

「名前付きパイプ」に対して複数プロセスから読み取りしても大丈夫なの?

逆も試してみます。
今度は複数プロセスから読み取りしてみます。

# ターミナルその1
$ while true; do
  cat np >> ps1.txt
done
# ターミナルその2
$ while true; do
  cat np >> ps2.txt
done
# ターミナルその3
$ while true; do
  cat np >> ps3.txt
done
# ターミナルその4
$ while true; do
  cat np >> ps4.txt
done

更にターミナルを1枚開き、以下のコマンドを実行してデータを名前付きパイプに書き込みます。

$ for ((i=0; i<40000; i++)); do
  echo $i > np
done
echo: write error: broken pipe
echo: write error: broken pipe
echo: write error: broken pipe
echo: write error: broken pipe
echo: write error: broken pipe
echo: write error: broken pipe
...

なんだかめちゃくちゃエラーが出てます。
結果を確認してみます。

$ wc -l ps*.txt
  6570 ps1.txt
  7889 ps2.txt
  6713 ps3.txt
  7967 ps4.txt
 29139 total

結果が全くあってないですね。なぜか出力漏れが発生しています。
名前付きパイプへの出力が漏れたのか、名前付きパイプからの入力が漏れたのか。

ひとこと

複数書き込みは使えそうでした。
複数読み込みは原因の調査が必要。

Bash, CentOS, Linux, Ubuntu

Posted by genzouw