当前位置:首页> 正文

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

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

Parallelize Bash script with maximum number of processes

让我们说我在Bash中有一个循环:

1
2
3
4
for foo in `some-command`
do
   do-something $foo
done

do-something是CPU绑定的,我有一个漂亮的闪亮4核处理器。我希望能够一次运行多达4个do-something \\。

天真的方法似乎是:

1
2
3
4
for foo in `some-command`
do
   do-something $foo &
done

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

您如何编写此循环,以便总是同时运行X个do-something


根据您要执行的操作,xargs也可以提供帮助(此处:使用pdf2ps转换文档):

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

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

来自文档:

1
2
3
4
5
6
--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.

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

1
some-command | parallel do-something

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

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

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

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

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

1
some-command | parallel do-something | postprocess

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 ...

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

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

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

1
2
3
4
for i in *; do
    do_something $i &
    pwait 10
done

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


使用Makefile代替普通的bash,然后使用make -jX指定同时执行的作业数,其中X是一次运行的作业数。

或者您可以使用wait(" man wait "):启动多个子进程,调用wait-当子进程完成时它将退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
maxjobs = 10

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

job ( ){
...
}

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


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


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

1
2
3
4
5
6
7
INPUT  = a.input b.input
OUTPUT = $(INPUT:.input=.output)

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

all: $(OUTPUT)

然后运行

1
make -j<NUMBER>

最多可并行运行NUMBER个命令。


虽然可能无法在bash中执行此正确操作,但您可以相当轻松地执行半正确操作。 bstark大致正确地表示了权利,但是他具有以下缺陷:

  • 分词:您不能将在其参数中使用以下任何字符的任何作业传递给它:空格,制表符,换行符,星号,问号。如果这样做,事情可能会崩溃,可能是意外的。
  • 它依赖于脚本的其余部分而不会后台运行任何内容。如果这样做了,或者以后再添加一些内容,该内容会在后台发送给脚本,因为您忘记了由于他的代码段而被禁止使用后台作业,事情将会中断。

没有这些缺陷的另一个近似值是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中不可能找到完美的解决方案。


bash功能:

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

使用:

1
cat my_commands | parallel -j 4

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


n


对于大多数目的来说这可能已经足够了,但并不是最佳选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/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

n


您可以使用一个简单的嵌套for循环(用下面的N和M替换适当的整数):

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

这将在M轮中执行do_something N * M次,每轮并行执行N个作业。您可以使N等于您拥有的CPU数量。


n


$ DOMAINS = "命令中某些域的列表"
对于some-command中的foo

1
2
3
4
5
eval `some-command for $DOMAINS` &

    job[$i]=$!

    i=$(( i + 1))

完成

Ndomains = echo $DOMAINS |wc -w

对于$(seq 1 1 $ Ndomains)中的i

echo "等待$ {job [$ i]} "
等待" $ {job [$ i]} "
已完成

在此概念中,

将用于并行化。重要的是评估的最后一行是\\'


展开全文阅读

相关内容