我的问题是如何更改此代码,使其仅使用 4 个子进程?
TESTS="a b c d e"
for f in $TESTS; do
t=$[ ( $RANDOM % 5 ) + 1 ]
sleep $t && echo $f $t &
done
wait
有趣的问题。我尝试使用 xargs 来实现此目的,并找到了一种方法。
试试这个:
seq 10 | xargs -i --max-procs=4 bash -c "echo start {}; sleep 3; echo done {}"
--max-procs=4
将确保同时运行的子进程不超过四个。
输出将如下所示:
start 2
start 3
start 1
start 4
done 2
done 3
done 1
done 4
start 6
start 5
start 7
start 8
done 6
done 5
start 9
done 8
done 7
start 10
done 9
done 10
请注意,执行顺序可能不会按照您提交命令的顺序执行。正如您所看到的,2 在 1 之前开始。
快速而肮脏的解决方案:将此行插入到
for
循环中的某个位置:
while [ $(jobs | wc -l) -ge 4 ] ; do sleep 1 ; done
(假设您还没有在同一个 shell 中运行其他后台作业)
我使用
parallel
(moreutils
包的一部分)找到了这个问题的另一个解决方案。)
parallel -j 4 -i bash -c "echo start {}; sleep 2; echo done {};" -- $(seq 10)
-j 4
代表-j maxjobs
-i
使用参数为 {}
--
界定你的论点
该命令的输出将是:
start 3
start 4
start 1
start 2
done 4
done 2
done 3
done 1
start 5
start 6
start 7
start 8
done 5
done 6
start 9
done 7
start 10
done 8
done 9
done 10
GNU Parallel 专为此类任务而设计:
TESTS="a b c d e"
for f in $TESTS; do
t=$[ ( $RANDOM % 5 ) + 1 ]
sem -j4 sleep $t && echo $f $t
done
sem --wait
观看介绍视频以了解更多信息:
您可以使用内置的
jobs
来执行类似的操作:
for f in $TESTS; do
running=($(jobs -rp))
while [ ${#running[@]} -ge 4 ] ; do
sleep 1 # this is not optimal, but you can't use wait here
running=($(jobs -rp))
done
t=$[ ( $RANDOM % 5 ) + 1 ]
sleep $t && echo $f $t &
done
wait
这个经过测试的脚本一次运行 5 个作业,并且一旦完成就会重新启动一个新作业(由于当我们收到 SIGCHLD 时睡眠 10.9 被终止。一个更简单的版本可以使用直接轮询(更改睡眠 10.9睡觉 1 并摆脱陷阱)。
#!/usr/bin/bash
set -o monitor
trap "pkill -P $$ -f 'sleep 10\.9' >&/dev/null" SIGCHLD
totaljobs=15
numjobs=5
worktime=10
curjobs=0
declare -A pidlist
dojob()
{
slot=$1
time=$(echo "$RANDOM * 10 / 32768" | bc -l)
echo Starting job $slot with args $time
sleep $time &
pidlist[$slot]=`jobs -p %%`
curjobs=$(($curjobs + 1))
totaljobs=$(($totaljobs - 1))
}
# start
while [ $curjobs -lt $numjobs -a $totaljobs -gt 0 ]
do
dojob $curjobs
done
# Poll for jobs to die, restarting while we have them
while [ $totaljobs -gt 0 ]
do
for ((i=0;$i < $curjobs;i++))
do
if ! kill -0 ${pidlist[$i]} >&/dev/null
then
dojob $i
break
fi
done
sleep 10.9 >&/dev/null
done
wait
这是我在 AIX 上使用 bash 的“并行”解压循环:
for z in *.zip ; do
7za x $z >/dev/null
while [ $(jobs -p|wc -l) -ge 4 ] ; do
wait -n
done
done
备注:
一个通用的答案,当存在大量(不是太多)长时间运行的作业并且仅使用 POSIX shell 功能和文件系统时很有用。
这是一个非常简单的作业处理器,将其安装在名称
queue
下:
#!/bin/sh
queue () {
ID="$1" JOBS="$2"
for JOB in "$JOBS"/*.job; do
mv "$JOB" "$JOB.$ID" 2>/dev/null || continue
sh "$JOB.$ID" && rm "$JOB.$ID"
done
}
for JOB in $(seq $1); do queue $JOB $2 & done
queue 4 workdir
为目录 workdir
启动四个作业处理器。
回答问题:
创建五个工作岗位:
for f in a b c d e; do
t=$((RANDOM % 5 + 1))
echo "sleep $t
echo $f $t" > workdir/job$((I+=1)).job
done
在四个子进程中处理它们并等待
queue 4 workdir; wait
如果作业失败,它将被留在
workdir/jobM.job.N
中以供检查或重复。 M
是作业的编号,N
是处理该作业的队列的编号。