可以说我在Bash中有一个循环:
for foo in `some-command` do do-something $foo done
do-something
是cpu绑定,我有一个漂亮闪亮的4核处理器.我希望能够一次跑到4 do-something
岁.
天真的做法似乎是:
for foo in `some-command` do do-something $foo & done
这将运行所有 do-something
s的一次,但有几个缺点,主要是做多岁的也有一些显著I/O执行其全部一次可能会慢一点.另一个问题是这个代码块立即返回,所以当所有的do-something
s完成时,没办法做其他工作.
你怎么写这个循环所以总是有X do-something
一次运行?
根据你想要做什么,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.
使用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
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 ...
使用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 ( ){ ... }
如果需要存储作业的结果,则将其结果分配给变量.在wait
您检查变量包含的内容之后.
这里有一个替代解决方案,可插入.bashrc并用于日常一个班轮:
function pwait() { while [ $(jobs -p | wc -l) -ge $1 ]; do sleep 1 done }
要使用它,所有人必须做的是放在&
作业和pwait调用之后,该参数给出了并行进程的数量:
for i in *; do do_something $i & pwait 10 done
使用wait
而不是忙于等待输出会更好jobs -p
,但似乎没有明显的解决方案等待任何给定的作业完成而不是全部.
也许尝试并行实用程序而不是重写循环?我是xjobs的忠实粉丝.我一直使用xjobs在我们的网络中批量复制文件,通常是在设置新的数据库服务器时. http://www.maier-komor.de/xjobs.html
虽然这样做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,kill -0
将不会注意到您的作业已完成并且事情将再次中断.
一个完美的解决方案是不可能的bash
.
如果您熟悉该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个命令.