web-dev-qa-db-ja.com

限られた数の子プロセスをbashで並行して実行していますか?

重い処理を行う必要のあるファイルのセットがたくさんあります。このシングルスレッドでの処理は、数百MiBのRAM(ジョブの開始に使用されたマシン上))を使用し、実行に数分かかります。現在のユースケースは、でhadoopジョブを開始することです。入力データですが、他のケースでも同じ問題が発生しました。

利用可能なCPUパワーを十分に活用するために、これらのタスクをいくつか並行して実行できるようにしたいと思います。

ただし、このような非常に単純なシェルスクリプトの例では、過度の負荷とスワッピングが原因でシステムパフォーマンスが低下します。

find . -type f | while read name ; 
do 
   some_heavy_processing_command ${name} &
done

だから私が欲しいのは本質的に「gmake-j4」がすることと似ています。

Bashが「wait」コマンドをサポートしていることは知っていますが、それはすべての子プロセスが完了するまで待機するだけです。過去に、「ps」コマンドを実行してから、子プロセスを名前でgrepするスクリプトを作成しました(はい、私は知っています...醜いです)。

私がやりたいことをするための最も簡単/クリーン/最良の解決策は何ですか?


編集:Frederikに感謝:はい、確かにこれは bashの関数で使用されるスレッド/サブプロセスの数を制限する方法の複製です "xargs --max-procs = 4 "はチャームのように機能します。 (それで私は自分の質問を閉じることに投票しました)

24
Niels Basjes
#! /usr/bin/env bash

set -o monitor 
# means: run background processes in a separate processes...
trap add_next_job CHLD 
# execute add_next_job when we receive a child complete signal

todo_array=($(find . -type f)) # places output into an array

index=0
max_jobs=2

function add_next_job {
    # if still jobs to do then add one
    if [[ $index -lt ${#todo_array[*]} ]]
    # apparently stackoverflow doesn't like bash syntax
    # the hash in the if is not a comment - rather it's bash awkward way of getting its length
    then
        echo adding job ${todo_array[$index]}
        do_job ${todo_array[$index]} & 
        # replace the line above with the command you want
        index=$(($index+1))
    fi
}

function do_job {
    echo "starting job $1"
    sleep 2
}

# add initial set of jobs
while [[ $index -lt $max_jobs ]]
do
    add_next_job
done

# wait for all jobs to complete
wait
echo "done"

Fredrikは、xargsがまさにあなたが望むことを実行するという優れた点を示しています...

20
Dunes

私はこの答えでパーティーに遅れていることを知っていますが、私は、スクリプトの本文をよりクリーンでシンプルにする代替案、IMHOを投稿すると思いました。 (明らかに、シナリオに適した値2と5を変更できます。)

function max2 {
   while [ `jobs | wc -l` -ge 2 ]
   do
      sleep 5
   done
}

find . -type f | while read name ; 
do 
   max2; some_heavy_processing_command ${name} &
done
wait
23
BruceH

GNU Parallelを使用すると、より簡単になります。

find . -type f | parallel  some_heavy_processing_command {}

詳細: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

9
Ole Tange

make を使用してより便利な解決策を見つけたと思います:

#!/usr/bin/make -f

THIS := $(lastword $(MAKEFILE_LIST))
TARGETS := $(Shell find . -name '*.sh' -type f)

.PHONY: all $(TARGETS)

all: $(TARGETS)

$(TARGETS):
        some_heavy_processing_command $@

$(THIS): ; # Avoid to try to remake this makefile

たとえば、それを呼び出します。 'test.mak'、および実行権を追加します。 ./test.makを呼び出すと、some_heavy_processing_commandが1つずつ呼び出されます。ただし、./test.mak -j 4として呼び出すと、一度に4つのサブプロセスが実行されます。また、より洗練された方法で使用することもできます。./test.mak -j 5 -l 1.5として実行すると、システム負荷が1.5未満のときに最大5つのサブプロセスが実行されますが、システム負荷が1.5を超えるとプロセス数が制限されます。 。

xargs よりも柔軟性があり、 make は、parallelとは異なり、標準分布の一部です。

4
TrueY

このコードは私にとって非常にうまく機能しました。

スクリプトを終了できないという問題に気づきました。 max_jobsが配列内の要素の数よりも大きいためにスクリプトが終了しない場合に遭遇した場合、スクリプトは決して終了しません。

上記のシナリオを防ぐために、「max_jobs」宣言の直後に次のものを追加しました。

if [ $max_jobs -gt ${#todo_array[*]} ];
    then
           # there are more elements found in the array than max jobs, setting max jobs to #of array elements"
            max_jobs=${#todo_array[*]}
 fi
3
masseo