具有最大进程数的并行Bash脚本

时间:2020-03-05 18:46:03  来源:igfitidea点击:

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

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

" do-something"受CPU约束,我有一个漂亮的闪亮4核处理器。我希望能够一次运行多达4个"做某事"。

天真的方法似乎是:

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

这将立即运行所有" do-something",但是有一些缺点,主要是do-something可能还具有一些重要的I / O,一次执行所有操作可能会放慢速度。另一个问题是该代码块立即返回,因此当所有的"完成某件事"完成后,就无法进行其他工作。

我们将如何编写此循环,以便总是同时运行Xdo-something

解决方案

回答

可以使用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 ( ){
...
}

如果需要存储作业的结果,则将其结果分配给变量。在等待之后,我们只需检查变量包含什么。

回答

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

回答

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

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

回答

我从事的项目使用wait命令来控制并行shell(实际上是ksh)进程。为了解决我们对IO的担忧,在现代OS上,并行执行实际上可能会提高效率。如果所有进程都读取磁盘上的相同块,则只有第一个进程必须运行物理硬件。其他进程通常将能够从内存中OS的磁盘缓存中检索该块。显然,从内存中读取要比从磁盘中读取快几个数量级。而且,该好处不需要任何编码更改。

回答

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

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

要使用它,所有要做的事就是在作业和pwait调用之后加上"&",该参数给出了并行进程的数量:

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

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

回答

虽然可能无法在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"的退出代码。

此代码的问题在于:

  • 它一次调度四个(在这种情况下)作业,然后等待所有四个作业结束。某些作业可能比其他作业更快地完成,这将导致下一批四个作业等待直到上一批作业中的最长作业完成。

解决最后一个问题的解决方案必须使用" kill -0"来轮询是否有任何进程已经消失,而不是wait并计划下一个作业。但是,这带来了一个新的小问题:我们在工作结束与kill -0检查任务是否结束之间存在竞争条件。如果作业结束并且系统上的另一个进程同时启动,并采用随机的PID(恰好是刚刚完成的作业的PID),则`kill -0'不会注意到作业已经完成,事情将会发生。再次休息。

在bash中不可能找到完美的解决方案。

回答

根据我们要执行的操作,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.

回答

如果我们熟悉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个命令。

回答

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

some-command | parallel do-something

GNU Parallel还支持在远程计算机上运行作业。即使远程计算机具有不同数量的内核,它也将在远程计算机上每个CPU内核运行一个内核:

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

一个更高级的示例:在这里,我们列出了要运行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"

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

some-command | parallel do-something | postprocess

观看视频以了解更多示例:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1