Bash:限制并发作业的数量?
有没有简单的方法来限制bash中的并发作业数量? 我的意思是当在后台运行n个以上的并发作业时,使得&阻塞。
我知道我可以用ps |来实现这个 grep式的技巧,但有没有更简单的方法?
如果你安装了GNU Parallel http://www.gnu.org/software/parallel/,你可以这样做:;
parallel gzip ::: *.log
它将每个CPU核心运行一个gzip,直到所有的日志文件都被压缩。
如果它是一个更大的循环的一部分,你可以使用sem
代替:
for i in *.log ; do echo $i Do more stuff here sem -j+0 gzip $i ";" echo done done sem --wait
它也会这样做,但给你一个机会,为每个文件做更多的东西。
如果您的发行版没有打包GNU Parallel,则可以通过以下方式安装GNU Parallel:
(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash
它将下载,签名,并进行个人安装,如果它不能全球安装。
观看GNU Parallel的介绍video了解更多信息: https : //www.youtube.com/playlist?list = PL284C9FF2488BC6D1
下面的脚本显示了一个用函数完成这个操作的方法。 您可以将bgxupdate
和bgxlimit
函数放在您的脚本中,或者将它们放在一个单独的文件中,该文件源自您的脚本:
. /path/to/bgx.sh
它的优点是可以独立维护多组进程(例如,可以运行一个限制为10的组,另一个完全独立的组限制为3)。
它使用bash
内置的jobs
来获取subprocess列表,但是将它们保存在单个variables中。 在底部的循环中,您可以看到如何调用bgxlimit
函数:
- 设置一个空的组variables。
- 转移到
bgxgrp
。 - 用你想要运行的限制和命令调用
bgxlimit
。 - 将新组转移回您的组variables。
当然,如果你只有一个组,直接使用bgxgrp
而不是bgxgrp
和传出。
#!/bin/bash # bgxupdate - update active processes in a group. # Works by transferring each process to new group # if it is still active. # in: bgxgrp - current group of processes. # out: bgxgrp - new group of processes. # out: bgxcount - number of processes in new group. bgxupdate() { bgxoldgrp=${bgxgrp} bgxgrp="" ((bgxcount = 0)) bgxjobs=" $(jobs -pr | tr '\n' ' ')" for bgxpid in ${bgxoldgrp} ; do echo "${bgxjobs}" | grep " ${bgxpid} " >/dev/null 2>&1 if [[ $? -eq 0 ]] ; then bgxgrp="${bgxgrp} ${bgxpid}" ((bgxcount = bgxcount + 1)) fi done } # bgxlimit - start a sub-process with a limit. # Loops, calling bgxupdate until there is a free # slot to run another sub-process. Then runs it # an updates the process group. # in: $1 - the limit on processes. # in: $2+ - the command to run for new process. # in: bgxgrp - the current group of processes. # out: bgxgrp - new group of processes bgxlimit() { bgxmax=$1 ; shift bgxupdate while [[ ${bgxcount} -ge ${bgxmax} ]] ; do sleep 1 bgxupdate done if [[ "$1" != "-" ]] ; then $* & bgxgrp="${bgxgrp} $!" fi } # Test program, create group and run 6 sleeps with # limit of 3. group1="" echo 0 $(date | awk '{print $4}') '[' ${group1} ']' echo for i in 1 2 3 4 5 6 ; do bgxgrp=${group1} ; bgxlimit 3 sleep ${i}0 ; group1=${bgxgrp} echo ${i} $(date | awk '{print $4}') '[' ${group1} ']' done # Wait until all others are finished. echo bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp} while [[ ${bgxcount} -ne 0 ]] ; do oldcount=${bgxcount} while [[ ${oldcount} -eq ${bgxcount} ]] ; do sleep 1 bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp} done echo 9 $(date | awk '{print $4}') '[' ${group1} ']' done
这是一个示例运行:
0 12:38:00 [ ] 1 12:38:00 [ 3368 ] 2 12:38:00 [ 3368 5880 ] 3 12:38:00 [ 3368 5880 2524 ] 4 12:38:10 [ 5880 2524 1560 ] 5 12:38:20 [ 2524 1560 5032 ] 6 12:38:30 [ 1560 5032 5212 ] 9 12:38:50 [ 5032 5212 ] 9 12:39:10 [ 5212 ] 9 12:39:30 [ ]
- 整个事情从12:38:00开始,正如你所看到的,前三个进程立即运行。
- 每个进程睡眠
n*10
秒,所以第四个进程直到第一个退出(时间t = 10或12:38:10)才开始。 在添加1560之前,您可以看到进程3368已从列表中消失。 - 类似地,当第二个(5880)在时间t = 20退出时,第五个过程(5032)开始。
- 最后,当第三个(2524)在时间t = 30退出时,第六个过程(5212)开始。
- 第四个过程在t = 50(从10开始,持续时间为40),第五个在t = 70(开始于20,持续时间50)和第六个在t = 90(从30开始,持续60 )。
或者,以时间线forms:
Process: 1 2 3 4 5 6 -------- - - - - - - 12:38:00 ^ ^ ^ 12:38:10 v | | ^ 12:38:20 v | | ^ 12:38:30 v | | ^ 12:38:40 | | | 12:38:50 v | | 12:39:00 | | 12:39:10 v | 12:39:20 | 12:39:30 v
一个小bash脚本可以帮助你:
# content of script exec-async.sh joblist=($(jobs -p)) while (( ${#joblist[*]} >= 3 )) do sleep 1 joblist=($(jobs -p)) done $* &
如果你打电话给:
. exec-async.sh sleep 10
…四次,前三个电话将立即返回,第四个电话将被阻止,直到有less于三个工作正在运行。
您需要在当前会话内通过加上前缀来启动这个脚本.
,因为jobs
只列出当前会话的作业。
里面的sleep
是丑陋的,但我没有find一种方法来等待终止的第一份工作。
假设你想写这样的代码:
for x in $(seq 1 100); do # 100 things we want to put into the background. max_bg_procs 5 # Define the limit. See below. your_intensive_job & done
max_bg_procs
应放在你的.bashrc
:
function max_bg_procs { if [[ $# -eq 0 ]] ; then echo "Usage: max_bg_procs NUM_PROCS. Will wait until the number of background (&)" echo " bash processes (as determined by 'jobs -pr') falls below NUM_PROCS" return fi local max_number=$((0 + ${1:-0})) while true; do local current_number=$(jobs -pr | wc -l) if [[ $current_number -lt $max_number ]]; then break fi sleep 1 done }
这对于大多数目的来说可能是足够好的,但并不是最佳的。
#!/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
这是最简单的方法:
waitforjobs() { while test $(jobs -p | wc -w) -ge "$1"; do wait -n; done }
在分出任何新工作之前调用这个函数:
waitforjobs 10 run_another_job &
要拥有与机器上核心一样多的背景作业,请使用$(nproc)
而不是像10这样的固定数字。
如果你愿意在纯粹的事情之外做到这一点,你应该看看一个工作排队系统。
例如,有GNU队列或PBS 。 而对于PBS,你可能要考虑毛伊岛的configuration。
两个系统都需要一些configuration,但是完全可以允许一次执行特定数量的作业,只有在正在运行的作业完成时才启动新排队的作业。 通常,这些作业排队系统将用于超级计算群集,在这些群集中,您希望为特定的批处理作业分配特定数量的内存或计算时间; 但是,没有理由不考虑计算时间或内存限制,不能在单台台式机上使用其中的一台。
您是否考虑过启动十个长时间运行的侦听器进程并通过命名pipe道与它们进行通信?
你可以使用ulimit -u参见http://ss64.com/bash/ulimit.html
在Linux上,我使用它来限制bash作业的可用CPU数量(可能通过设置CPU_NUMBER
覆盖)。
[ "$CPU_NUMBER" ] || CPU_NUMBER="`nproc 2>/dev/null || echo 1`" while [ "$1" ]; do { do something with $1 in parallel echo "[$# items left] $1 done" } & while true; do # load the PIDs of all child processes to the array joblist=(`jobs -p`) if [ ${#joblist[*]} -ge "$CPU_NUMBER" ]; then # when the job limit is reached, wait for *single* job to finish wait -n else # stop checking when we're below the limit break fi done # it's great we executed zero external commands to check! shift done # wait for all currently active child processes wait
下面的函数(从tangens开发回答上面,要么复制到脚本或源文件):
job_limit () { # Test for single positive integer input if (( $# == 1 )) && [[ $1 =~ ^[1-9][0-9]*$ ]] then # Check number of running jobs joblist=($(jobs -rp)) while (( ${#joblist[*]} >= $1 )) do # Wait for any job to finish command='wait '${joblist[0]} for job in ${joblist[@]:1} do command+=' || wait '$job done eval $command joblist=($(jobs -rp)) done fi }
1)只需要插入一行来限制现有的循环
while : do task & job_limit `nproc` done
2)等待完成现有的后台任务而不是轮询,提高快速任务的效率