いくつかのプロセスを実行するとします。
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
上記のスクリプトを次のように実行します。
foobarbaz | cat
私の知る限り、プロセスのいずれかがstdout / stderrに書き込むとき、それらの出力はインターリーブしません-stdioの各行はアトミックであるようです。それはどのように機能しますか?各行のアトミック性を制御するユーティリティは何ですか?
いくつかのプロセスを実行するとします。
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
上記のスクリプトを次のように実行します。
foobarbaz | cat
私の知る限り、プロセスのいずれかがstdout / stderrに書き込むとき、それらの出力はインターリーブしません-stdioの各行はアトミックであるようです。それはどのように機能しますか?各行のアトミック性を制御するユーティリティは何ですか?
回答:
インターリーブします!短い出力バーストのみを試みましたが、これは分割されないままですが、実際には、特定の出力が分割されないことを保証するのは困難です。
プログラムがどのように出力をバッファリングするかによります。stdioライブラリ彼らが使用するバッファを書いているとき、ほとんどのプログラムは出力をより効率的に使用します。プログラムがライブラリ関数を呼び出してファイルに書き込むとすぐにデータを出力する代わりに、関数はこのデータをバッファに保存し、バッファがいっぱいになるとデータを実際に出力します。これは、出力がバッチで実行されることを意味します。より正確には、3つの出力モードがあります。
プログラムは各ファイルを再プログラムして異なる動作をすることができ、明示的にバッファーをフラッシュできます。プログラムがファイルを閉じるか、正常に終了すると、バッファは自動的にフラッシュされます。
同じパイプに書き込むすべてのプログラムが行バッファーモードを使用するか、非バッファーモードを使用して各行を出力関数の1回の呼び出しで書き込み、行が単一のチャンクに書き込むのに十分な場合、出力は行全体のインターリーブになります。ただし、プログラムの1つが完全バッファモードを使用している場合、または行が長すぎる場合は、混合行が表示されます。
これは、2つのプログラムからの出力をインターリーブする例です。LinuxではGNU coreutilsを使用しました。これらのユーティリティの異なるバージョンは異なる動作をする場合があります。
yes aaaa
aaaa
基本的に行バッファモードと同等の方法で永久に書き込みます。yes
ユーティリティは、実際には一度に複数の行を書き込み、それは出力を発するたびに、出力は、行の整数です。echo bbbb; done | grep b
bbbb
完全バッファモードで永久に書き込みます。8192のバッファサイズを使用し、各行の長さは5バイトです。5は8192を分割しないため、書き込み間の境界は一般に行境界にありません。それらを一緒に売り込みましょう。
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
ご覧のとおり、grepが中断されることがあり、その逆もあります。行の約0.001%だけが中断されましたが、それは起こりました。出力はランダム化されているため、割り込みの数は異なりますが、毎回少なくともいくつかの割り込みが発生しました。バッファーあたりの行数が減少すると中断の可能性が高くなるため、行が長くなると中断された行の割合が高くなります。
出力バッファリングを調整する方法はいくつかあります。主なものは次のとおりです。
stdbuf -o0
GNU coreutilsやその他のFreeBSDなどのシステムにあるプログラムで、デフォルト設定を変更せずにstdioライブラリを使用するプログラムのバッファリングをオフにします。または、でラインバッファリングに切り替えることもできstdbuf -oL
ます。unbuffer
。一部のプログラムは、他の方法で異なる動作をする場合があります。たとえばgrep
、出力が端末の場合、デフォルトで色を使用します。--line-buffered
GNU grepに渡すなどして、プログラムを構成します。上記のスニペットをもう一度見てみましょう。今回は、両側でラインバッファリングを行います。
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
このため、yesはgrepを中断しませんでしたが、grepがyesを中断することもありました。理由は後で説明します。
各プログラムが一度に1行を出力し、行が十分に短い限り、出力行はきれいに分離されます。しかし、これが機能するための行の長さには制限があります。パイプ自体には転送バッファーがあります。プログラムがパイプに出力すると、データはライタープログラムからパイプの転送バッファーにコピーされ、その後パイプの転送バッファーからリーダープログラムにコピーされます。(少なくとも概念的には、カーネルはこれを単一のコピーに最適化する場合があります。)
パイプの転送バッファーに収まるよりも多くのデータがコピーされる場合、カーネルは一度に1バッファーフルをコピーします。複数のプログラムが同じパイプに書き込みを行っており、カーネルが選択する最初のプログラムが複数のバッファーフルを書き込みたい場合、カーネルが再度同じプログラムを選択するという保証はありません。たとえば、Pがバッファサイズで、foo
2 * Pバイトbar
を書き込み、3バイトを書き込みたい場合、可能なインターリーブは、Pバイトfrom foo
、次に3バイトfrom bar
、Pバイトfrom foo
です。
上記のyes + grepの例に戻ると、私のシステムでyes aaaa
は、一度に8192バイトのバッファーに収まるだけの行を書き込むことがあります。書き込む5バイト(印刷可能な4文字と改行)があるため、毎回8190バイトを書き込むことになります。パイプバッファサイズは4096バイトです。したがって、yesから4096バイトを取得し、grepからいくつかの出力を取得し、yesから残りの書き込みを取得することができます(8190-4096 = 4094バイト)。4096バイトを使用するaaaa
と、819行分のスペースが1つだけ残りa
ます。したがって、この単独の行のa
後にgrepからの書き込みが1つあり、の行が表示されabbbb
ます。
何が起こっているかの詳細を確認したい場合getconf PIPE_BUF .
は、システムのパイプバッファーサイズを通知し、各プログラムによって行われたシステムコールの完全なリストを表示できます。
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
行の長さがパイプバッファのサイズよりも小さい場合、行のバッファリングにより、出力に混合行が存在しないことが保証されます。
行の長さが長くなる可能性がある場合、複数のプログラムが同じパイプに書き込んでいるときに任意の混合を避ける方法はありません。分離を確実にするには、各プログラムに異なるパイプに書き込み、プログラムを使用して行を結合する必要があります。たとえば、GNU Parallelはデフォルトでこれを行います。
cat
、catプロセスがfoo / bar / bazから行全体を受け取るが、別の行から半分の行を受け取らないように、すべての行がアトミックに書き込まれることを保証する良い方法かもしれません。 bashスクリプトでできることはありますか?
awk
、同じIDに対して2行(またはそれ以上)の出力find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
がfind -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
生成されたが、IDごとに1行のみが正しく生成された場合にも当てはまります。
http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-Pはこれを調べました:
GNU xargsは、複数のジョブの並列実行をサポートしています。-P nここで、nは並行して実行するジョブの数です。
seq 100 | xargs -n1 -P10 echo "$a" | grep 5 seq 100 | xargs -n1 -P10 echo "$a" > myoutput.txt
これは多くの状況で正常に機能しますが、不正な欠陥があります:$ aに〜1000文字を超える文字が含まれる場合、エコーはアトミックではない可能性があり(複数のwrite()呼び出しに分割される可能性があります)、2行のリスクがあります混合されます。
$ perl -e 'print "a"x2000, "\n"' > foo $ strace -e write bash -c 'read -r foo < foo; echo "$foo"' >/dev/null write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 1008) = 1008 write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 993) = 993 +++ exited with 0 +++
echoまたはprintfの呼び出しが複数ある場合、明らかに同じ問題が発生します。
slowprint() { printf 'Start-%s ' "$1" sleep "$1" printf '%s-End\n' "$1" } export -f slowprint seq 10 | xargs -n1 -I {} -P4 bash -c "slowprint {}" # Compare to no parallelization seq 10 | xargs -n1 -I {} bash -c "slowprint {}" # Be sure to see the warnings in the next Pitfall!
各ジョブは2つ(またはそれ以上)の個別のwrite()呼び出しで構成されるため、並列ジョブからの出力は混合されます。
したがって、出力を混在させないでおく必要がある場合は、出力のシリアル化を保証するツール(GNU Parallelなど)を使用することをお勧めします。
xargs echo
echo bashビルトインではなく、echo
からのユーティリティを呼び出します$PATH
。とにかく、bash 4.4でそのbashエコー動作を再現できません。Linuxでは、4Kより大きいパイプ(/ dev / nullではない)への書き込みは、アトミックであるとは限りません。