スクリプトのマルチバックグラウンドプロセス

スクリプトのマルチバックグラウンドプロセス

一部のファイルをコピーするのに時間がかかる状況が発生した場合は、ファイルのコピーに対して並列処理を実行する必要があるとします。たとえば、次のようになります。

for i in ipaddresslist
do

cp x y &  //running in back ground or some other process in background

done

wait  //will cause till all the files are copied or whatever process if all are finished

これで、wait すべてのバックグラウンドプロセスが完了しましたが、次のような場合があります。

1)一部のファイルのコピーがより早く発生する可能性があるため、これらのファイルに対していくつかの処理を実行する必要がある場合は、すべてのファイルがコピーされるのを待つ必要があります。

2)レプリケーションプロセス(またはバックグラウンドで実行されている他のプログラム)がログファイルに書き込むと、各バックグラウンドプロセスが同時にファイルに書き込もうとするため、ログファイルが壊れる可能性があります。

この種の問題に対する解決策はありますか? 1) プロセスが完了したことがわかっている場合、プロセスが完了すると、プロセスの特定のインスタンス (ファイルのコピーなど) で残りの処理タスクを開始できます。また、ログファイルの書き込みは順次発生することがあります。

提案してください

ベストアンサー1

一部のファイルをコピーした後に一部のジョブを開始する必要がある場合は、そのジョブをバックグラウンドジョブの一部として作成してください。

(cp this /there && start job that needs this in /there) &
(cp that /here && start job that needs that in /here) &
wait

(最後は&必須ではありません。)

これで、より複雑な依存関係にGNUを使用できますmake -j

make -j2 -f /dev/fd/3 3<< 'EOF'
all: j1 j2 j3
.PHONY: cp1 cp2 cp3 j1 j2 j3 all

cp1:
    cp this /there

cp2:
    cp that /here

cp3:
    cp this /here

j1: cp1
    start job that needs this in /there

j2: cp2
    start job that needs that in /here

j3: cp1 cp3
    start job that needs this in /here and /there
EOF

-j2いつでも最大2つのジョブを実行でき、依存関係は尊重されます。

これで、誤ったログファイルを防ぐために2つの主なオプションがあります。

  1. インターリーブしないでください。つまり、各割り当ての内容を1つずつ追加します。
  2. どの行がどのタスクに属しているかを簡単に確認できるように、各タスクの各行にラベルを付けて、すばらしくインターリーブされていることを確認してください。

1の場合、最も簡単な方法は、各ジョブ出力を別々のファイルに保存してマージすることです。

(cp this /there && start job that needs this in /there) > j1.log 2>&1 &
(cp that /here && start job that needs that in /here) > j2.log 2>&1 &
wait
cat j1.log j2.log > jobs.log

別のオプションは、パイプラインを使用して各ジョブの出力を収集してマージすることですcat。で利用可能なシェルプロセスの置き換えは、kshこれを達成するのに役立ち、zshバックbashグラウンドでも処理できます。

j1() { cp this /there && start job that needs this in /there; }
j2() { cp that /here && start job that needs that in /here; }
cat <(j1 2>&1) <(j2 2>&1) > jobs.log

j1、同時に開始され、パイプラインと相互接続されますj2cat

catただし、2番目のパイプ(作成者)からの読み込みは完了後にのみ開始されますj2j1つまり、j2パイプサイズ(Linuxでは通常64kiBなど)よりも多くのログレコードが書き込まれると、完了するj2までブロックされますj1

spongeこれはfromを使用して防ぐことができますmoreutils。たとえば、次のようになります。

cat <(j1 2>&1) <(j2 2>&1 | sponge) > jobs.log

これは、すべての出力がメモリに保存され、catはj2inが完了した後にのみ出力の書き込みを開始することを意味しますが、この場合、例を使用する方が良いかもしれません。j2jobs.logj2pv -qB 100M

cat <(j1 2>&1) <(j2 2>&1 | pv -qB 100M) > jobs.log

このアプローチは、まだ完了していない場合、j2出力(および両方のパイプの内容を含む)を記録した後にのみ一時停止し、j1標準出力への出力が完了するのを待ちません。100Mpvj2

上記のように、ほとんどのコマンド出力をファイルまたはパイプ(tty以外のすべて)にリダイレクトすると、動作が影響を受けることに注意してください。これらのコマンドまたは呼び出すstdioAPI libcprintf、、、 ...)はfputsfwrite出力が端末に送信されないことを検出し、これらの標準ミスを実行していない間に大きな出力チャンク(キロバイト)を渡すことによって最適化を実行します。これは、出力とエラーメッセージの順序が影響を受けることを意味します。これが問題になると、GNUシステムまたはFreeBSD(少なくとも)および動的リンクコマンドの場合は、次のものを使用できますstdbuf

stdbuf -oL j1 > j1.log 2>&1

変える:

j1 > j1.log 2>&1

stdio出力がラインバッファリングされていることを確認してください(各出力ラインは完了するとすぐに個別に書き込まれます)。

オプション2の場合、PIPE_BUFバイトより小さいパイプ(Linuxでは4096バイト、ログの平均行よりはるかに大きい)に書き込むことが原子性を保証します。言い換えれば、両方のプロセスが同時に同じパイプに書き込む場合、それらの書き込みは原子性を保証します。両方の書き込みが互いに絡み合わないように保証されます。通常のファイルについてはそのような保証はありませんが、4kiBより小さい2つの書き込みがどのOSやファイルシステムで絡み合うことができるか真剣に疑われます。

したがって、上記のバッファリングなしでログ行が完全に個別に出力される場合、行出力には、このジョブの行の一部であり、他のジョブの一部であるパスがないことが保証されます。

ただし、コマンドが作成中の行の2つの部分(たとえば)の間をフラッシュするのを防ぐ方法はなく、printf("foo"); fflush(stdout); printf("bar\n");stderrのバッファリングもありません。

もう1つの問題は、すべてのタスクの行がインターリーブされると、どの行がどのタスクに対応するかを知ることが難しいことです。

次の手順で両方の問題を解決できます。

tag() { stdbuf -oL sed "s%^%$1: %"; }
{
  j1 2>&1 | tag j1 &
  j2 2>&1 | tag j2
} | cat > jobs.log

(誰ももはやパイプに書き込むまで完了しないので、これは必要ありません(ほとんどwaitのシェルでは機能しません)。catj1j2

上記では、| cat原子性を確保するパイプラインを使用しました。各コマンドの出力をコマンドにパイプします。商標各行には役職があります。必要に応じて出力を作成できます。j1なぜなら、(タグプレフィックスを持つ)行は全体として個別に出力されるため、出力が破損しないようにするからです。j2sedstdbuf -oL

stdbuf -oL上記と同じ説明が依然として適用される。コマンドには適用されないため、出力をバッファリングする可能性が高いため、生成されてから長い時間が経過するまで記録されない可能性がありますj1j2これは、次の理由で前のケースよりも悪いです。

j1: doing A
j1: doing B
j2: doing C

これは、Bが実行される前にAが実行されることを意味しますj1が、Cが実行される前に命令を実行するという意味ではありません。したがって、問題が発生した場合は、j2より多くのコマンドを適用する必要があるかもしれません。stdbuf -oL

このようなシェル機能には適用できませんが、stdbuf少なくともGNUおよびFreeBSD stdbufでは、これを使用してグローバルまたはサブシェルごとに設定できます。j1j2stdbuf

stdbuf_LD_PRELOAD=$(stdbuf sh -c 'export -p LD_PRELOAD')
line_buffered_output() {
  eval "$stdbuf_LD_PRELOAD"
  export _STDBUF_O=L
}
j1() (line_buffered_output; cp this /there && start...)

おすすめ記事