2つのfifoから読み取ろうとします(他のfifoに内容がない場合は1つのfifoから読み込み、両方に内容がない場合は後でもう一度やり直してください)。プロセスはブロックされ続けます(タイムアウトオプションがあっても)。
私はファイルの読み取りに関するいくつかの異なる質問に従いました。whileループを使用して2つの入力ファイルを読み取る方法とループ中にIFSで2つのファイルを読み取る)今、次のコードがあります。
while true; do
while
IFS= read -r msg; statusA=$?
IFS= read -u 3 -r socket; statusB=$?
[ $statusA -eq 0 ] || [ $statusB -eq 0 ]; do
if [ ! -z "$msg" ]; then
echo "> $msg"
fi
if [ ! -z "$socket" ]; then
echo ">> $socket"
fi
done <"$msg_fifo" 3<"$socket_fifo"
done
私は何が間違っていましたか?またpaste
、/パイプが利用できないか、cat
プロセスが完全にブロックされます。
答え1
これは基本設計であり、それが利用可能かどうかはわかりません(select()システムコールモジュールがあります)を使用してこの問題をbash
解決できますが、zsh
現在のシェルはその操作に適した言語ではない可能性があります。 。
問題はUnixのすべてと同様に単純ですが、実際の理解と解決にはより深い思考と実用的な知識が必要です。
これは、VFS オブジェクトを開くときにシステムカーネルによってデフォルトで実行されるファイル記述子にブロックフラグが設定されているためです。
Unixでは、プロセスコラボレーションはIO境界のブロックを通じて同期されます。これは非常に直感的で、すべてを非常にエレガントでシンプルにし、素朴なシンプルなプログラミングを中級および中級のアプリケーションプログラマに「ただ動作させる」ことを可能にします。
問題のオブジェクト(ファイル、fifoなど)を開くと、そのオブジェクトに設定されているブロックフラグは、読み取るデータがないときにディスクリプタから読み取られるすべての内容がすぐにプロセス全体をブロックすることを保証します。このブロックは、一部のデータが反対側でオブジェクトに埋め込まれている場合にのみブロック解除されます(パイプの場合)。
通常のファイルは、少なくともパイプと比較して「例外」です。 IOサブシステムの観点からは「絶対ブロック」しないからです(実際にブロックしても - つまり、ファイルfdからブロックフラグをブロック解除しても効果がありません.プロセスがブロックするからです.カーネルのストレージfd読み込みルーチンより深いところで発生します)。プロセスPOVのディスク読み取りは常に即時にゼロタイムでブロックされません(その読み取り中にシステムクロックが実際にジャンプしても)。
このデザインの2つの効果を観察します。
まず、シェルが通常のファイルを処理するときは、IOブロックの効果を実際に観察することはできません(上記のようにファイルは実際にブロックされないためです)。
次に、ここに示すように、シェルのパイプでread()をブロックすると、プロセスはデフォルトで永久にブロックされます。ブロックは、少なくとも後でまで多くのデータが満たされないためです。パイプの反対側からこちらです。この状態では、プロセスが実行されず、CPU時間も消費されず、カーネルはより多くのデータが到着するまで外部からプロセスをブロックするため、プロセスタイムアウトルーチンも実行できません。つまり、プロセスがCPU時間を消費する必要があるからです。 )。
少なくとも読み取り可能な十分なデータでパイプを埋めるまで、プロセスはブロックされたままになり、すべてのデータが再び消費され、プロセスが再びブロックされるまでしばらくブロックが解除されます。
考えてみると、これが実際にシェルのパイプが機能する理由です。
複雑なシェルパイプラインが高速または遅いプログラムにどのように適応するのか疑問に思いましたか?これが機能するようにするメカニズムです。出力をすばやくエクスポートするクイックジェネレータはパイプラインの次のプログラムをより速く読み込み、パイプラインの遅いデータジェネレータはパイプラインの後続のプログラムの読み書きを遅くします。すべてがデータブロックの影響を受けます。パイプ全体の同期は魔法のように機能します。
編集:追加の説明
問題から抜け出す方法?
簡単な方法はありません。私が知る限り、bashにはありません。
最も簡単な方法は、問題についてもっと考え、別の方法で再構成することです。
ブロッキングの性質が上で説明されているので、シェルプログラムによって課される設計制約を理解するのが最も簡単です。つまり、メイン入力ストリームは1つだけです。
これにより、ファイル入力(問題なし)とパイプを処理できるほどシェルが強力になります。
複数のパイプ(2つのパイプなど)から読み取ると、両方のデータがあるまでプログラムが自然にブロックされるため、両方のパイプが常にデータでいっぱいであることを確認できれば、これはうまくいきます。残念ながら、これはほとんど動作しません。パイプから読み取るときに絡み合ってインターリーブされると、パイプがランダムな順序で埋められる問題に直面します。特に、最初のパイプが空の場合、読み取りが依存している場合はプロセス全体がブロックされます。私たちはこの状況をデッドロックと呼びます。
問題のあるファイル記述子からブロックフラグを削除することで、複数のパイプから読み取る問題を解決できますが、これを処理するために適切に実装された言語を必要とするIOスケジューリングとデータの多重化の問題があります。
残念ながら、bashはまだ十分に装備されていません。それでも、この機能がどのように機能するかをもっと理解する必要があります。
答え2
@etosanと@ilkkachu間の会話を見て、それを使用するというあなたの提案をテストしたところ、これはうまくexec fd<>fifo
いきます。 @etosanが言ったように、これがどのような問題に関連しているかはわかりませんが、少なくとも今はうまくいきます。
exec 3<>"$socket_fifo" # to not block the read
while true; do
while
IFS= read -t 0.1 -r msg; statusA=$?
IFS= read -t 0.1 -u 3 -r socket; statusB=$?
[ $statusA -eq 0 ] || [ $statusB -eq 0 ]; do
if [ ! -z "$msg" ]; then
echo "> $msg"
fi
if [ ! -z "$socket" ]; then
echo ">> $socket"
fi
done <"$msg_fifo"
done
この状況で Bash を使用することに関する警告も考慮します。