すべての入力を読んだ後、FIFOのすべてのリーダーをどのように閉じますか?そのうちの1つだけをオフにしてプログラムが完了しないようです。
以下は、動作するサンプルプログラムです(ファイルに入れるテスト用)。
set -euo pipefail
rm -f todo.pipe
mkfifo todo.pipe
rm -f output.pipe
mkfifo output.pipe
cat todo.pipe | \
while read line && echo hej $line; do :; done \
> output.pipe &
echo "adam\n bertil\n carl" > todo.pipe &
cat < output.pipe
出力は予想通りです。
❯ ./test.zsh
hej adam
hej bertil
hej carl
❯
しかし、これらのタスクを処理するために別のスレッドを追加すると、タスクtodo.pipe
は永久に中断されます。
set -euo pipefail
rm -f todo.pipe
mkfifo todo.pipe
rm -f output.pipe
mkfifo output.pipe
cat todo.pipe | \
while read line && echo hej $line; do :; done \
> output.pipe &
# The below 3 lines is all that's changed
cat todo.pipe | \
while read line && echo hej $line; do :; done \
> output.pipe &
echo "adam\n bertil\n carl" > todo.pipe &
cat < output.pipe
これで以前と同じ内容を印刷しますが、決して戻りません。なぜ?この問題をどのように解決できますか?
私は2番目の「ワーカースレッド」が現在EOFまたはそれに似ていることを疑っていますが、ここでは基本的なものを見逃しているようです。
答え1
FIFOブロックを読み取りモードで読み取るのではなく、開くと、他のプロセスも書き込みモードで開くまで(またはその逆)ブロックされ、そのような場合にパイプがインスタンス化されることを認識することが重要です。
パイプがアクティブな間にFIFOを開いて、より多くのプロセスをパイプに接続できます。
どのプロセスでもパイプが開かない場合、パイプは破壊され、その後最初のパーティに戻り、読み取りと書き込みのためにfifoが再び開くと、他のパイプがインスタンス化される可能性があります。
存在する:
[0] [1] cat todo.pipe | [2] while read line && echo hej $line; do :; done \ > output.pipe & # The below 3 lines is all that's changed [3] cat todo.pipe | [4] while read line && echo hej $line; do :; done \ > output.pipe & [5] echo "adam\n bertil\n carl" > todo.pipe & [6] cat < output.pipe
メインシェルプロセスは同時に4つのプロセスを作成し、各プロセスは独立して並列に実行されます。最初は最初のパイプを実行し、2番目は2番目のパイプを実行し、3番目は実行しecho
、4番目はcat
(開いた後output.pipe
)実行します。
cat todo.pipe
パイププロセスはまた、元のプロセスが同時にループしている間に実行する追加のプロセスを作成しますwhile
。
したがって、6つ(最後のプロセスを待つメインシェルプロセスを計算すると7つcat
)がほとんど同時に開始されます。私は[1]
それらを上に..とマークしました[6]
。
予約方法は、システムのプロセススケジューラによって異なります。外部コマンド(たとえば、cat
時間のかかるコマンド)を実行するには、シェル自体で実行される操作が最初に発生する可能性があります。
2、4、5、6はすべてシェルでfifoファイルを開き始めます。 2と4はoutput.pipe
書き込みと6
読み取りのために開いています。すぐに互いのロックが解除され、パイプがインスタンス化されます。
todo.pipe
5は、少なくとも1つのcat
プロセスが読み取り専用で開くまで書き込み専用のオープン状態を一時停止します。
これにより、1と3がこの点をめぐって競争するようになります。実行には、プロセスメモリの消去、ディスクから実行可能ファイルのロード、ライブラリ共有、動的接続、動的接続の実行、最後にそのコマンドラインが解析され、fifoファイルが最終的にその中にあるコードの実行がcat
含まれます。開く。/bin/cat
cat
1または3のいずれかがFIFOを開くと(ここでは1と仮定)、5つのロックが解除されます。 1はこのfdに対して作業をread()
続けますが、まだパイプに何もないので停止します。
5番は現在予定されているプロセスかもしれません。これはecho
シェルの組み込みコマンドを実行しているので、aを実行してwrite("adam...)
終了し、fdがそれを閉じますoutput.pipe
。
その後、read()
続行してcat
大きな塊を読み取り、小さな出力全体を飲み込み、パイプの書き込みread()
の終わりまでfd'ingすることを含めて終了できます。
3がそれまでfifoを開かない場合、パイプは破壊され、3がついにfifoを開くと、他のものが書き込みモードでfifoを開き、ここで配管が発生しない関連のない新しいパイプをインスタンス化するまで停止します。 。
最初に開かないと、output.pipe
fifoのような問題が発生する可能性があります。
今やっても:
{
cat | while...done &
cat | while...done
} < todo.pipe > output.pipe &
echo ... > todo.pipe &
cat < output.pipe
where はtodo.pipe
読み取るために一度だけ開かれるので、両方ともcat
fd を共有します ( と同じoutput.pipe
)、これらの問題を避けるので、おそらくあまり役に立ちません。
cat
最初のタスクを実行するタスクはread()
出力echo
全体を飲み込み、他のタスクには何もしません。echo
読み取りバッファよりも大きな出力に置き換えて、両方のバッファcat
にそれぞれいくつかのフラグメントをキャッチする機会を与えても、cat
各フラグメントは見かけ上ランダムな方法で切り捨てられます。
組み込み関数が一度に1バイトずつ読み取るため、sがパイプから直接読み取られるようにcat |
sを削除すると、状況はさらに悪化します。したがって、競合する2つのsが順番に1バイトを読み取ることになります。read
read
read
これが機能する唯一の方法は、次のジョブが入力される前に最初のジョブがその1つによって読み取られるようにcat
プロセスが十分に遅いことを確認し、システムコールを介して一度に1つのToDoエントリを入力することです。 4KiBより大きくないジョブの場合、読み取りバッファサイズよりも大きくはありません。todo.pipe
cat
write()
cat
より良いアプローチは、1つのプロセスがパイプを読み取り、xargs -P
GNUやGNUなどのものを使用してタスクをワーカーに渡すことですparallel
。