96

Bashにループがあるとしましょう:

for foo in `some-command`
do
   do-something $foo
done

do-somethingCPUにバインドされており、光沢のある4コアプロセッサがあります。4台まで同時に走れるようにしたいdo-somethingです。

素朴なアプローチは次のようです。

for foo in `some-command`
do
   do-something $foo &
done

これはすべて do-somethingの s を一度に実行しますが、いくつかの欠点があります。主に、do-something には重要な I/O が含まれている可能性があり、一度にすべて実行すると少し遅くなる可能性があります。もう 1 つの問題は、このコード ブロックがすぐに返されるため、すべてdo-somethingの が終了したときに他の作業を行う方法がないことです。

do-somethingXが同時に実行されるように、このループをどのように記述しますか?

4

16 に答える 16

69

やりたいことによっては、xargs も役立ちます (ここでは、pdf2ps でドキュメントを変換します):

cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps

ドキュメントから:

--max-procs=max-procs
-P max-procs
       Run up to max-procs processes at a time; the default is 1.
       If max-procs is 0, xargs will run as many processes as  possible  at  a
       time.  Use the -n option with -P; otherwise chances are that only one
       exec will be done.
于 2009-05-19T07:50:04.573 に答える
41

GNU Parallel http://www.gnu.org/software/parallel/を使用すると、次のように記述できます。

some-command | parallel do-something

GNU Parallelは、リモートコンピューターでのジョブの実行もサポートしています。これにより、リモートコンピューターのCPUコアごとに1つ実行されます。コアの数が異なっていても、次のようになります。

some-command | parallel -S server1,server2 do-something

より高度な例:ここでは、my_scriptを実行するファイルのリストを示します。ファイルの拡張子は(多分.jpeg)です。my_scriptの出力をbasename.out内のファイルの横に配置する必要があります(例:foo.jpeg-> foo.out)。my_scriptをコンピューターのコアごとに1回実行し、ローカルコンピューターでも実行します。リモートコンピューターの場合、ファイルを処理して特定のコンピューターに転送する必要があります。my_scriptが終了したら、foo.outを転送して戻し、次にfoo.jpegとfoo.outをリモートコンピューターから削除します。

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"

GNU Parallelは、各ジョブからの出力が混ざらないようにするため、出力を別のプログラムの入力として使用できます。

some-command | parallel do-something | postprocess

その他の例については、ビデオを参照してください:https ://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

于 2010-06-10T01:37:03.403 に答える
22
maxjobs=4
並列化 () {
        while [ $# -gt 0 ] ; 行う
                jobcnt=(`ジョブ -p`)
                if [ ${#jobcnt[@]} -lt $maxjobs ] ; それから
                        何かをする $1 &
                        シフト  
                そうしないと
                        睡眠 1
                フィ
        終わり
        待つ
}

parallelize arg1 arg2 "5 つの args から 3 番目のジョブ" arg4 ...
于 2008-09-01T18:00:43.140 に答える
17

ここでは、.bashrcに挿入して、日常のワンライナーに使用できる代替ソリューションを示します。

function pwait() {
    while [ $(jobs -p | wc -l) -ge $1 ]; do
        sleep 1
    done
}

これを使用するには&、ジョブとpwait呼び出しの後に行う必要があるすべてのことです。パラメーターは、並列プロセスの数を示します。

for i in *; do
    do_something $i &
    pwait 10
done

waitの出力を待つのではなく、使用する方がよいでしょうがjobs -p、すべてではなく、指定されたジョブのいずれかが終了するまで待つという明白な解決策はないようです。

于 2009-05-19T03:40:40.603 に答える
11

単純な bash の代わりに、Makefile を使用して、同時に実行するジョブの数を X で指定しますmake -jX。ここで、X は一度に実行するジョブの数です。

waitまたは、 (" ")を使用できますman wait: 複数の子プロセスを起動し、呼び出しますwait。子プロセスが終了すると終了します。

maxjobs = 10

foreach line in `cat file.txt` {
 jobsrunning = 0
 while jobsrunning < maxjobs {
  do job &
  jobsrunning += 1
 }
wait
}

job ( ){
...
}

ジョブの結果を保存する必要がある場合は、その結果を変数に割り当てます。wait変数の内容を確認した後。

于 2008-09-01T16:50:18.397 に答える
8

ループを書き直す代わりに、並列化ユーティリティを試してみませんか? 私はxjobsの大ファンです。私は常に xjobs を使用して、ネットワーク全体でファイルを大量コピーします。通常は、新しいデータベース サーバーをセットアップするときです。 http://www.maier-komor.de/xjobs.html

于 2008-09-01T16:55:04.763 に答える
7

コマンドに慣れている場合はmake、ほとんどの場合、実行したいコマンドのリストを makefile として表現できます。たとえば、それぞれが *.output を生成するファイル *.input に対して $SOME_COMMAND を実行する必要がある場合、makefile を使用できます。

INPUT = a.入力 b.入力
OUTPUT = $(INPUT:.input=.output)

%。出力入力
    $(SOME_COMMAND) $< $@

すべて: $(出力)

そして、ただ実行します

make -j<NUMBER>

最大 NUMBER 個のコマンドを並行して実行できます。

于 2009-05-21T20:33:30.260 に答える
6

これを正しく行うことbashはおそらく不可能ですが、セミライトはかなり簡単に行うことができます。 bstark権利の公正な概算を与えましたが、彼には次の欠陥があります。

  • 単語分割: 引数に次の文字を使用するジョブを渡すことはできません: スペース、タブ、改行、星、疑問符。そうすると、おそらく予期せずに物事が壊れます。
  • スクリプトの残りの部分に依存して、何もバックグラウンドにしないようにします。彼のスニペットのためにバックグラウンドジョブの使用が許可されていないことを忘れていたために、バックグラウンドで送信されるスクリプトに何かを追加した場合、または後でスクリプトに何かを追加すると、問題が発生します。

これらの欠陥を持たない別の概算は次のとおりです。

scheduleAll() {
    local job i=0 max=4 pids=()

    for job; do
        (( ++i % max == 0 )) && {
            wait "${pids[@]}"
            pids=()
        }

        bash -c "$job" & pids+=("$!")
    done

    wait "${pids[@]}"
}

これは、終了時に各ジョブの終了コードをチェックするように簡単に適応できるため、ジョブが失敗した場合にユーザーに警告したり、scheduleAll失敗したジョブの量に応じて終了コードを設定したりできます。

このコードの問題は、次のとおりです。

  • 一度に 4 つのジョブ (この場合) をスケジュールし、4 つすべてが終了するまで待機します。一部のジョブは他のジョブよりも早く実行される可能性があり、その場合、4 つのジョブの次のバッチは、前のバッチの中で最も長いジョブが実行されるまで待機します。

この最後の問題に対処するソリューションは、次のジョブのkill -0代わりにプロセスのいずれかが消えたかどうかをポーリングしてスケジュールする必要があります。waitただし、これにより、小さな新しい問題が発生します。ジョブの終了と、ジョブが終了したkill -0かどうかの確認の間に競合状態が発生します。ジョブが終了し、システム上の別のプロセスが同時に開始され、たまたま終了したばかりのジョブの PID であるランダムな PID が取得された場合、 はジョブが終了したことにkill -0気付かず、再び問題が発生します。

では完全な解決は不可能ですbash

于 2009-05-19T07:26:10.167 に答える
3

関数 bash:

parallel ()
{
    awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\t@-\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make $@ -f - all
}

使用:

cat my_commands | parallel -j 4
于 2012-02-22T10:14:33.200 に答える
2

私が取り組んでいるプロジェクトでは、waitコマンドを使用して並列シェル (実際には ksh) プロセスを制御しています。IO に関する懸念に対処するために、最新の OS では、並列実行によって実際に効率が向上する可能性があります。すべてのプロセスがディスク上の同じブロックを読み取っている場合、物理ハードウェアにアクセスする必要があるのは最初のプロセスだけです。他のプロセスは、多くの場合、メモリ内の OS のディスク キャッシュからブロックを取得できます。明らかに、メモリからの読み取りは、ディスクからの読み取りよりも数桁高速です。また、利点にはコーディングの変更は必要ありません。

于 2008-09-03T23:19:27.773 に答える
1

ほとんどの目的にはこれで十分ですが、最適ではありません。

#!/bin/bash

n=0
maxjobs=10

for i in *.m4a ; do
    # ( DO SOMETHING ) &

    # limit jobs
    if (( $(($((++n)) % $maxjobs)) == 0 )) ; then
        wait # wait until all have finished (not optimal, but most times good enough)
        echo $n wait
    fi
done
于 2011-07-21T09:05:13.303 に答える
1

これが、bashスクリプトでこの問題を解決する方法です。

 #! /bin/bash

 MAX_JOBS=32

 FILE_LIST=($(cat ${1}))

 echo Length ${#FILE_LIST[@]}

 for ((INDEX=0; INDEX < ${#FILE_LIST[@]}; INDEX=$((${INDEX}+${MAX_JOBS})) ));
 do
     JOBS_RUNNING=0
     while ((JOBS_RUNNING < MAX_JOBS))
     do
         I=$((${INDEX}+${JOBS_RUNNING}))
         FILE=${FILE_LIST[${I}]}
         if [ "$FILE" != "" ];then
             echo $JOBS_RUNNING $FILE
             ./M22Checker ${FILE} &
         else
             echo $JOBS_RUNNING NULL &
         fi
         JOBS_RUNNING=$((JOBS_RUNNING+1))
     done
     wait
 done
于 2015-10-13T16:52:51.877 に答える
0

一定数のプロセスを常に実行し続け、エラーを追跡し、無停電/ゾンビプロセスを処理する私のソリューション:

function log {
    echo "$1"
}

# Take a list of commands to run, runs them sequentially with numberOfProcesses commands simultaneously runs
# Returns the number of non zero exit codes from commands
function ParallelExec {
    local numberOfProcesses="${1}" # Number of simultaneous commands to run
    local commandsArg="${2}" # Semi-colon separated list of commands

    local pid
    local runningPids=0
    local counter=0
    local commandsArray
    local pidsArray
    local newPidsArray
    local retval
    local retvalAll=0
    local pidState
    local commandsArrayPid

    IFS=';' read -r -a commandsArray <<< "$commandsArg"

    log "Runnning ${#commandsArray[@]} commands in $numberOfProcesses simultaneous processes."

    while [ $counter -lt "${#commandsArray[@]}" ] || [ ${#pidsArray[@]} -gt 0 ]; do

        while [ $counter -lt "${#commandsArray[@]}" ] && [ ${#pidsArray[@]} -lt $numberOfProcesses ]; do
            log "Running command [${commandsArray[$counter]}]."
            eval "${commandsArray[$counter]}" &
            pid=$!
            pidsArray+=($pid)
            commandsArrayPid[$pid]="${commandsArray[$counter]}"
            counter=$((counter+1))
        done


        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            # Handle uninterruptible sleep state or zombies by ommiting them from running process array (How to kill that is already dead ? :)
            if kill -0 $pid > /dev/null 2>&1; then
                pidState=$(ps -p$pid -o state= 2 > /dev/null)
                if [ "$pidState" != "D" ] && [ "$pidState" != "Z" ]; then
                    newPidsArray+=($pid)
                fi
            else
                # pid is dead, get it's exit code from wait command
                wait $pid
                retval=$?
                if [ $retval -ne 0 ]; then
                    log "Command [${commandsArrayPid[$pid]}] failed with exit code [$retval]."
                    retvalAll=$((retvalAll+1))
                fi
            fi
        done
        pidsArray=("${newPidsArray[@]}")

        # Add a trivial sleep time so bash won't eat all CPU
        sleep .05
    done

    return $retvalAll
}

使用法:

cmds="du -csh /var;du -csh /tmp;sleep 3;du -csh /root;sleep 10; du -csh /home"

# Execute 2 processes at a time
ParallelExec 2 "$cmds"

# Execute 4 processes at a time
ParallelExec 4 "$cmds"
于 2016-08-28T08:30:57.300 に答える
0

ネストされた単純な for ループを使用できます (以下の N と M を適切な整数に置き換えます)。

for i in {1..N}; do
  (for j in {1..M}; do do_something; done & );
done

これにより、do_something N*M 回が M ラウンドで実行され、各ラウンドで N 個のジョブが並行して実行されます。N は、使用している CPU の数と等しくすることができます。

于 2011-11-19T19:49:16.377 に答える