使用最大进程数并行化Bash脚本

the*_*sdj 83 bash

可以说我在Bash中有一个循环:

for foo in `some-command`
do
   do-something $foo
done
Run Code Online (Sandbox Code Playgroud)

do-something是cpu绑定,我有一个漂亮闪亮的4核处理器.我希望能够一次跑到4 do-something岁.

天真的做法似乎是:

for foo in `some-command`
do
   do-something $foo &
done
Run Code Online (Sandbox Code Playgroud)

这将运行所有 do-something s的一次,但有几个缺点,主要是做多岁的也有一些显著I/O执行其全部一次可能会慢一点.另一个问题是这个代码块立即返回,所以当所有的do-somethings完成时,没办法做其他工作.

你怎么写这个循环所以总是有X do-something一次运行?

Fri*_*ner 57

根据你想要做什么,xargs也可以提供帮助(这里:使用pdf2ps转换文档):

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

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps
Run Code Online (Sandbox Code Playgroud)

来自文档:

--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.
Run Code Online (Sandbox Code Playgroud)

  • 在我看来,这种方法是最优雅的解决方案.除了,因为我是偏执狂,我总是喜欢使用`find [...] -print0`和`xargs -0`. (8认同)
  • `cpus = $(getconf _NPROCESSORS_ONLN)` (7认同)

Ole*_*nge 38

使用GNU Parallel http://www.gnu.org/software/parallel/,您可以编写:

some-command | parallel do-something
Run Code Online (Sandbox Code Playgroud)

GNU Parallel还支持在远程计算机上运行作业.这将在远程计算机上为每个CPU核心运行一个 - 即使它们具有不同数量的核心:

some-command | parallel -S server1,server2 do-something
Run Code Online (Sandbox Code Playgroud)

一个更高级的示例:这里我们列出了我们希望运行my_script的文件.文件有扩展名(可能是.jpeg).我们希望my_script的输出放在basename.out中的文件旁边(例如foo.jpeg - > foo.out).我们想为计算机的每个核心运行一次my_script,我们也希望在本地计算机上运行它.对于远程计算机,我们希望将文件处理传输到给定的计算机.当my_script完成时,我们希望将foo.out转回,然后我们要从远程计算机中删除foo.jpeg和foo.out:

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"
Run Code Online (Sandbox Code Playgroud)

GNU Parallel确保每个作业的输出不会混合,因此您可以将输出用作另一个程序的输入:

some-command | parallel do-something | postprocess
Run Code Online (Sandbox Code Playgroud)

有关更多示例,请参阅视频:https://www.youtube.com/playlist?list = PL284C9FF2488BC6D1


小智 23

maxjobs=4
parallelize () {
        while [ $# -gt 0 ] ; do
                jobcnt=(`jobs -p`)
                if [ ${#jobcnt[@]} -lt $maxjobs ] ; then
                        do-something $1 &
                        shift  
                else
                        sleep 1
                fi
        done
        wait
}

parallelize arg1 arg2 "5 args to third job" arg4 ...

  • 意识到这里有一些**严重**的不足之处,所以任何需要参数空间的工作都会失败; 此外,如果请求的工作量超过maxjobs允许的话,这个脚本会等待一些工作完成,从而使你的CPU活着. (10认同)

sko*_*ima 11

使用Makefile,而不是普通bash,然后指定同时作业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 ( ){
...
}
Run Code Online (Sandbox Code Playgroud)

如果需要存储作业的结果,则将其结果分配给变量.在wait您检查变量包含的内容之后.


Gru*_*bel 11

这里有一个替代解决方案,可插入.bashrc并用于日常一个班轮:

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

要使用它,所有人必须做的是放在&作业和pwait调用之后,该参数给出了并行进程的数量:

for i in *; do
    do_something $i &
    pwait 10
done
Run Code Online (Sandbox Code Playgroud)

使用wait而不是忙于等待输出会更好jobs -p,但似乎没有明显的解决方案等待任何给定的作业完成而不是全部.


小智 8

也许尝试并行​​实用程序而不是重写循环?我是xjobs的忠实粉丝.我一直使用xjobs在我们的网络中批量复制文件,通常是在设置新的数据库服务器时. http://www.maier-komor.de/xjobs.html


lhu*_*ath 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[@]}"
}
Run Code Online (Sandbox Code Playgroud)

请注意,此作业很容易适用于检查每个作业结束时的退出代码,以便您可以在作业失败时警告用户,或scheduleAll根据失败的作业数量设置退出代码等.

这段代码的问题就在于:

  • 它一次安排四个(在这种情况下)作业,然后等待所有四个结束.有些可能比其他一些更快完成,这将导致下一批四个作业等到上一批的最长时间完成.

解决这个最后一个问题的解决方案必须用于kill -0轮询是否有任何进程已经消失而不是wait并安排下一个作业.但是,这引入了一个小问题:在作业结束和kill -0检查是否结束之间存在竞争条件.如果作业结束并且系统上的另一个进程同时启动,则采用恰好是刚刚完成的作业的随机PID,kill -0将不会注意到您的作业已完成并且事情将再次中断.

一个完美的解决方案是不可能的bash.


Ide*_*lic 6

如果您熟悉该make命令,则大多数情况下您可以将要作为makefile运行的命令列表表达出来.例如,如果您需要在文件*.input上运行$ SOME_COMMAND,每个文件都生成*.output,您可以使用makefile

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

%.output : %.input
    $(SOME_COMMAND) $< $@

all: $(OUTPUT)

然后跑

make -j<NUMBER>

并行运行最多NUMBER个命令.