在bash中并行运行有限数量的子进程?

时间:2011-07-06 08:29:27

标签: bash parallel-processing

我有一大堆文件,需要进行一些繁重的处理。 这种单线程处理使用几百MiB的RAM(在用于启动作业的机器上)并且需要几分钟才能运行。 我目前的用例是在输入数据上启动一个hadoop作业,但我之前在其他情况下遇到了同样的问题。

为了充分利用可用的CPU功率,我希望能够在并列中运行多个这些任务。

然而,像这样的一个非常简单的示例shell脚本会因负载过大和交换而导致系统性能下降:

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

所以我想要的基本上与“gmake -j4”的相似。

我知道bash支持“wait”命令但只等待直到所有子进程都已完成。在过去,我创建了执行“ps”命令的脚本,然后按名称grep子进程(是的,我知道......丑陋)。

做我想做的最简单/最干净/最好的解决方案是什么?


编辑:感谢弗雷德里克:是的,这确实是How to limit number of threads/sub-processes used in a function in bash的副本 “xargs --max-procs = 4”就像一个魅力。 (所以我投票结束了自己的问题)

7 个答案:

答案 0 :(得分:22)

我知道我已经迟到了这个答案,但我想我会发布一个替代方案,恕我直言,使脚本的主体更清洁,更简单。 (显然,您可以将值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

答案 1 :(得分:19)

#! /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"

说过弗雷德里克非常重视xargs完全符合你想要的......

答案 2 :(得分:9)

使用GNU Parallel,它变得更简单:

find . -type f | parallel  some_heavy_processing_command {}

了解详情:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

答案 3 :(得分:4)

我认为我使用找到了一个更方便的解决方案:

#!/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。但是你可以调用./test.mak -j 4,然后它会一次运行四个子进程。您也可以以更复杂的方式使用它:以./test.mak -j 5 -l 1.5运行,然后在系统负载低于1.5时运行最多5个子进程,但如果系统负载超过1.5,它将限制进程数

它比更灵活,是标准发布的一部分,而不是parallel

答案 4 :(得分:3)

这段代码对我来说效果很好。

我注意到一个脚本无法结束的问题。 如果由于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

答案 5 :(得分:-1)

另一种选择:

PARALLEL_MAX=...
function start_job() {
  while [ $(ps --no-headers -o pid --ppid=$$ | wc -l) -gt $PARALLEL_MAX ]; do
    sleep .1  # Wait for background tasks to complete.                         
  done
  "$@" &
}
start_job some_big_command1
start_job some_big_command2
start_job some_big_command3
start_job some_big_command4
...

答案 6 :(得分:-2)

这是一个非常好的函数,我用来控制bash或ksh中的最大作业数。注意:pgrep中的-1减去了wc -l子进程。

function jobmax
{
    typeset -i MAXJOBS=$1
    sleep .1
    while (( ($(pgrep -P $$ | wc -l) - 1) >= $MAXJOBS ))
    do
        sleep .1
    done
}

nproc=5
for i in {1..100}
do
    sleep 1 &
    jobmax $nproc
done
wait # Wait for the rest