Bashスクリプトで一度に100のプロセスを開始する

Bashスクリプトで一度に100のプロセスを開始する

Bashスクリプトには次のプログラムがあります

for i in {1..1000}
do
   foo i
done

パラメータを使用して関数をfoo1000回呼び出します。i

複数のプロセスで実行されますが、同時に実行されないようにするにはどうすればよいですか?

だから私が持っているなら

for i in {1..1000}
do
   foo i &
done

1000のプロセスがすべて同時に開始されますが、これは私が望むものではありません。

常に100個のプロセスを実行する方法はありますか?一部のプロセスが完了すると、1000回の繰り返しがすべて完了するまで、いくつかの新しいプロセスが開始されます。または、100個すべてが完了するのを待ってから100個を実行することもできます。

答え1

zsh代わりに使用してくださいbash

autoload -Uz zargs
zargs -P100 -I{} -- {1..1000} -- foo {}

しかし、GNUがある場合は、xargs次のようにすることもできます(zshksh93またはからbash):

xargs -I{} -P100 -a <(echo {1..1000}) foo {}

fooしかしそれは独立した命令でなければならない。シェル関数や組み込み関数では使用できません。

zsh'はzargs順番に実行されます。 100ジョブを開始し、すべてのジョブが返されるのを待ってから、100ジョブの次の配置を開始します。一方、GNUはxargs最大100個のジョブを実行し続けようとします。 100 件のジョブを開始し、別のジョブを開始して一度に 1 つずつ完了します。

この動作を実現するには、zshからバックグラウンドプロセスが返されるたびにトリガーされるxargsSIGCHLDでジョブプールを起動して管理できます。trap

(
  todo=( {1..1000} ) max=100

  TRAPCHLD() {
    while (( $#jobstates < max && $#todo )); do
      foo $todo[1] & shift 1 todo
    done
  }

  : start &
  while (( $#todo )) wait
)

ここでは、新しいタスクのリストを取得するためにサブシェルでそれを実行する必要があります。 TRAPCHLDトラップの実行中にSIGCHLDがブロックされるため、トラップはそれ自体を再入力しないでください。これにより、競合状態やリストへの同時アクセスを防止する必要性を回避する必要があります$todo

答え2

グループとして実行できる場合は、ループを入れ子にします。

#! /bin/bash

date '+%T.%N'
for j in {1..3}; do
    for k in {1..3}; do
        (( ++i ))
        ( sleep 2.0 && printf 'Foo %d\n' $i ) &
    done
    wait
    date '+%T.%N'
    printf 'Batch %d ends\n' $j 
done
date '+%T.%N'

結果は時間の重複を示しています。

$ ./aBatch
19:55:17.078476713
Foo 1
Foo 2
Foo 3
19:55:19.094302514
Batch 1 ends
Foo 4
Foo 6
Foo 5
19:55:21.114530543
Batch 2 ends
Foo 7
Foo 9
Foo 8
19:55:23.132184671
Batch 3 ends
19:55:23.135792952
$ 

これはGNU並列性でも同様です。この方法の利点は、実行が異なる時間に実行されると、バッチ処理の他のparallelプロセスを待つ必要なしに追加のプロセスが開始されることです。

#! /bin/bash
#.. The script ./aFoo

    sleep 2 && printf 'Foo %d\n' $1

注文する:

$ date '+%T.%N'; parallel -j 3 ./aFoo -- {1..9}; date '+%T.%N'
20:11:44.446042653
Foo 3
Foo 1
Foo 2
Foo 4
Foo 5
Foo 6
Foo 7
Foo 8
Foo 9
20:11:50.503324162
$ 

答え3

以下は100単位に分割する簡単な方法です。 (bash)

for i in {1..1000}
do
   foo "$i" &
   (( i % 100 )) || wait
done
wait

同じシェルで実行される他のバックグラウンドジョブがないとし、100個のジョブを開始し、すべてのジョブが完了するのを待ち、もう100個のジョブを開始し、最後に残りのジョブが完了するのを待ちます。 (1000と100は残りがありませんが、他の場合はあります)

++nループ変数が数値でない場合は、代わりに式で使用できますi

n=0
for i in *
do
   foo "$i" &
   (( ++n % 100 )) || wait
done
wait

答え4

GNU Parallelはこのような状況のために作られました:

parallel foo ::: {1..1000}

foo n1000個のジョブがすべて実行されるまで、CPUスレッドごとに1つずつ(n = 1..1000)実行されます。あるジョブが完了すると、別のジョブが開始されます。

parallel -j100 foo ::: {1..1000}

foo n1000個のジョブがすべて実行されるまで、100個(ここでn = 1..1000)が実行されます。

GNU Parallel は出力を直列化するので、2 つのfoos が同時に印刷されても出力は歪ませません。

GNU Parallelには、並列化を容易にする他の多くの機能があります。 1章と2章を読むのに20分かかります。https://zenodo.org/record/1146014あなたのコマンドラインが感謝します。

関連情報