如何在bash中并行化for循环来限制进程数

时间:2016-08-04 18:00:14

标签: bash for-loop parallel-processing

我有一个类似于:

的bash脚本
NUM_PROCS=$1
NUM_ITERS=$2

for ((i=0; i<$NUM_ITERS; i++)); do
    python foo.py $i arg2 &
done

将并行流程数量限制为NUM_PROCS的最简单方法是什么?我正在寻找一种不需要软件包/安装/模块(如GNU Parallel)的解决方案。

当我尝试Charles Duffy的最新方法时,我从bash -x中得到以下错误:

+ python run.py args 1
+ python run.py ... 3
+ python run.py ... 4
+ python run.py ... 2
+ read -r line
+ python run.py ... 1
+ read -r line
+ python run.py ... 4
+ read -r line
+ python run.py ... 2
+ read -r line
+ python run.py ... 3
+ read -r line
+ python run.py ... 0
+ read -r line

...继续使用介于0和5之间的其他数字,直到启动了太多进程来处理系统并关闭bash脚本。

6 个答案:

答案 0 :(得分:7)

作为一个非常简单的实现,取决于具有wait -n的新版本的bash(等待只有下一个作业退出,而不是等待所有作业):

#!/bin/bash
#      ^^^^ - NOT /bin/sh!

num_procs=$1
num_iters=$2

declare -A pids=( )

for ((i=0; i<num_iters; i++)); do
  while (( ${#pids[@]} >= num_procs )); do
    wait -n
    for pid in "${!pids[@]}"; do
      kill -0 "$pid" &>/dev/null || unset "${pids[$pid]}"
    done
  done
  python foo.py "$i" arg2 & pids["$!"]=1
done

如果在没有wait -n的shell上运行,可以(效率非常低)用sleep 0.2之类的命令替换它,每隔1/5秒轮询一次。

由于您实际上是从文件中读取输入,另一种方法是启动N个子进程,每个进程只在(linenum % N == threadnum)处开始:

num_procs=$1
infile=$2
for ((i=0; i<num_procs; i++)); do
  (
    while read -r line; do
      echo "Thread $i: processing $line"
    done < <(awk -v num_procs="$num_procs" -v i="$i" \
                 'NR % num_procs == i { print }' <"$infile")
  ) &
done
wait # wait for all $num_procs subprocesses to finish

答案 1 :(得分:6)

bash 4.4将有一种有趣的新型参数扩展,简化了Charles Duffy的答案。

#!/bin/bash

num_procs=$1
num_iters=$2
num_jobs="\j"  # The prompt escape for number of jobs currently running
for ((i=0; i<num_iters; i++)); do
  while (( ${num_jobs@P} >= num_procs )); do
    wait -n
  done
  python foo.py "$i" arg2 &
done

答案 2 :(得分:4)

GNU,macOS / OSX,FreeBSD和NetBSD都可以使用xargs -P执行此操作,无需bash版本或软件包安装。这里一次有4个流程:

printf "%s\0" {1..10} | xargs -0 -I @ -P 4 python foo.py @ arg2

答案 3 :(得分:1)

您是否知道如果您被允许编写并运行自己的脚本,那么您还可以使用GNU Parallel吗?实质上,它是一个文件中的Perl脚本。

来自自述文件:

  

=最小安装次数=

     

如果你只是需要并行而且没有安装'make'(也许是   系统是旧的或Microsoft Windows):

wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem
mv parallel sem dir-in-your-$PATH/bin/
seq $2 | parallel -j$1 python foo.py {} arg2

parallel --embed(自20180322开始提供)甚至可以将GNU Parallel作为shell脚本的一部分进行分发(即不需要额外的文件):

parallel --embed >newscript

然后编辑newscript的结尾。

答案 4 :(得分:0)

一种相对简单的方法,只需另外两行代码即可完成此任务。说明是内联的。

NUM_PROCS=$1
NUM_ITERS=$2

for ((i=0; i<$NUM_ITERS; i++)); do
    python foo.py $i arg2 &
    let 'i>=NUM_PROCS' && wait -n # wait for one process at a time once we've spawned $NUM_PROC workers
done
wait # wait for all remaining workers

答案 5 :(得分:0)

这不是最简单的解决方案,但是如果您的bash版本没有“ wait -n”,并且您不想使用其他程序,例如parallel,awk等,则可以使用while和for循环。

num_iters=10
total_threads=4
iter=1
while [[ "$iter" -lt "$num_iters" ]]; do
    iters_remainder=$(echo "(${num_iters}-${iter})+1" | bc)
    if [[ "$iters_remainder" -lt "$total_threads" ]]; then
        threads=$iters_remainder
    else
        threads=$total_threads
    fi
    for ((t=1; t<="$threads"; t++)); do
        (
            # do stuff
        ) &
        ((++iter))
    done 
    wait
done