参考
https://www.linuxidc.com/Linux/2011-03/33918.htm
http://www.sohu.com/a/161607089_610671
https://blog.youkuaiyun.com/yeweiouyang/article/details/52512522
http://www.cnblogs.com/yxzfscg/p/5330136.html
背景
因项目需要,将shell中执行的脚本并行,但是使用&执行导致机器卡住,程序发生异常直接退出;
看来是并行度太大导致资源不够用,如何实现控制并行度的并行
过程
循环里套循环
在for循环中再嵌套一个for循环,在内部for循环后添加wait等待内部for循环结束后再进行下一轮循环,这样就会导致内部for循环中某个进程执行过慢就会导致内部循环的执行时间等于这个慢进程的执行时间,最终导致整个脚本的执行效率低。
#!/bin/bash
count=8 #总进程数
paracount=5 #并行度
size=$((${count}/${paracount}))
for((i=1;i<=${size};i++))
do
for((j=1;j<=${paracount};j++))
do {
echo $j
sleep $j
}&
done
wait
done
wait
for((i=1;i<=$((${count}%${paracount}));i++))
do {
echo $i
sleep $i
}&
done
wait
echo "done"
模拟队列控制进程数
将需要并列的子进程PID作为队列元素,限定队列大小;轮询队列,检测到队列大小小于并行度时启动下一个进程,直至所有子进程结束。
#!/bin/bash
count=100 #总数
paracount=5 #并行度
#将PID值追加到队列中
function PushQue(){
Que="${Que} $1"
Nrun=$(($Nrun+1))
}
#更新队列信息,先清空队列信息,然后检索生成新的队列信息
function GenQue(){
oldQue=$Que
Que="";Nrun=0
for PID in $oldQue;do
if [[ -d /proc/$PID ]];then
PushQue $PID
fi
done
}
#检查队列信息,如果有已经结束的进程,更新队列信息
function CheckQue(){
oldQue=$Que
for PID in $oldQue;do
if [[ ! -d /proc/$PID ]];then
GenQue;break
fi
done
}
for((i=1;i<=${count};i++));do
{ #并行内容
echo "$i,sleep $(($i%5))s"
sleep $(($i%5))
} &
PID=$!
PushQue $PID
while [[ $Nrun -ge $paracount ]];do
CheckQue
sleep 1
done
done
wait
echo "done"
使用fifo管道特性控制
#!/bin/bash
count=20 #总数
paracount=5 #并行度
tempfifo=$$.fifo # $$表示当前执行文件的PID
trap "exec 1000>&-;exec 1000<&-;exit 0" 2
mkfifo $tempfifo
exec 1000<>$tempfifo
rm -rf $tempfifo
for ((i=1;i<=${paracount};i++))
do
echo >&1000
done
for((i=1;i<=${count};i++));do
read -u1000
{
echo "$i,sleep $(($i%5))s"
sleep $(($i%5))
echo >&1000
} &
done
wait
echo "done"
简单解释
在这个过程中用到了 管道,文件描述符
trap "exec 1000>&-;exec 1000<&-;exit 0" 2
在脚本运行过程中,如果接收到Ctrl+C中断命令,则关闭文件描述符1000的读写,并正常退出 ;
exec 1000>&-;表示关闭文件描述符1000的写
exec 1000<&-;表示关闭文件描述符1000的读
trap是捕获中断命令
mkfifo $tempfifo
exec 1000<>$tempfifo
rm -rf $tempfifo
创建一个管道文件
将文件描述符1000与FIFO进行绑定,<读的绑定,>写的绑定,<>则标识对文件描述符1000的所有操作等同于对管道文件$tempfifo的操作
为什么不直接使用管道文件呢?事实上这并非多此一举,管道的一个重要特性,就是读写必须同时存在,缺失某一个操作,另一个操作就是滞留,绑定文件描述符(读、写绑定)正好解决了这个问题
read -u1000
读取管道中的一行,在这里就是读取一个空行;每次读取管道就会减少一个空行
echo >&1000
执行完后台任务之后,往文件描述符1000中写入一个空行。这是关键所在了,由于read -u1000每次操作,都会导致管道减少一个空行,当linux后台放入了8个任务之后,由于文件描述符1000没有可读取的空行,将导致read -u1000一直处于等待