Linux-Shell-使用mkfifo实现多任务并发及并发数控制

默认的情况下,Shell脚本中的命令是串行执行的,必须等到前一条命令执行完后才执行接下来的命令,但是如果我有一大批的的命令需要执行,而且互相又没有影响的情况下(有影响的话就比较复杂了),那么就要使用命令的并发执行了。

如下:

#!/bin/bash
IPLIST=/home/meta/ipinfo/iplist
for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}')
do
    ssh $i "cd ~/update/;tar zxf patch-20160909.tgz -C ~/LMDG/ && echo '/$i ok' || echo '/$i bad'"
done >> result.txt

echo "resutl"|mutt -a result.txt -s update-result meta@126.com

对于上面的代码,因为 iplist 中有好多ip,每个”tar zxf”都挺耗时的,所以打算使用并发编程,这样就可以节省大量时间了。

修改如下:

#!/bin/bash
IPLIST=/home/meta/ipinfo/iplist
for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}')
do
    ssh $i "cd ~/update/;tar zxf patch-20160909.tgz -C ~/LMDG/ && echo '/$i ok' || echo '/$i bad'" &
done >> result.txt

echo "resutl"|mutt -a result.txt -s update-result meta@126.com

加上“&” 之后 “tar zxf”就可以并行执行了。 实质是将”tar zxf” 作为后台进程在执行,这样该命令就不会占用当前bash,其他命令也不用等待前面命令执行完再继续了,而且可以放入多个任务到后台,这样就实现了多任务并发。

我本来目的是让”tar zxf”这个循环都执行结束后,再“mutt”前面的结果。如果像上面这样写的话,在”tar zxf”都还没结束时就已经开始执行“mutt”了,得到了错误的结果,因此需要做如下修改:

#!/bin/bash
IPLIST=/home/meta/ipinfo/iplist
for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}')
do
    ssh $i "cd ~/update/;tar zxf patch-20160909.tgz -C ~/LMDG/ && echo '/$i ok' || echo '/$i bad'" &
done >> result.txt
wait
echo "resutl"|mutt -a result.txt -s update-result meta@126.com

这里添加了“wait” 之后就可以达到我们预期的效果了,wait的作用就是等待子任务都执行完之后在结束父任务,继而执行下面的任务。

但是,紧接着又有问题了,如果这个iplist中的量巨大,这样一口气都放到后台,系统超出负载后,会有性能变差或者宕机风险,因此我们需要一个控制并发数的机制。
因此我们引入了任务队列的概念,有点类似之前socket举例中的消费者生产者模型,通过消息队列来调节供需的不平衡
修改如下:

#!/bin/bash

IPLIST=/home/meta/ipinfo/iplist #任务(消费者)
THREAD=50                       #声明并发线程并发个数,这个是此应用的关键,也就是设置管道的最大任务数
TMPFIFO=/tmp/$$.fifo			#声明管道名称,'$$'表示脚本当前运行的进程PID
mkfifo $TMPFIFO                #创建管道
exec 5<>${TMPFIFO}             #创建文件标示符“5”,这个数字可以为除“0”、“1”、“2”之外的所有未声明过的字符,以读写模式操作管道文件;系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变,换句话说就是在调用进程内部执行一个可执行文件
rm -rf ${TMPFIFO}              #清除创建的管道文件

#为并发线程创建同样个数的占位
for((i=1;i<=$THREAD;i++))
do
   echo ;               
 #借用read命令一次读取一行的特性,使用一个echo默认输出一个换行符,来确保每一行只有一个线程占位;这里让人联想到生产者&消费者模型,管道文件充当消息队列,来记录消费者的需求,然后由生产者去领任务,并完成任务,这里运用了异步解耦的思想。
done >&5                
 #将占位信息写入管道

for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}') #从任务队列中依次读取任务
do
        read -u5        
 #从文件描述符管道中,获取一个管道的线程占位然后开始执行操作;read中 -u 后面跟fd,表示从文件描述符中读入,该文件描述符可以是exec新开启的。
        {
               echo $(cat ~/ipinfo/iplist|grep $i|awk '{print $2}');
               ssh -oConnectTimeout=10 -oConnectionAttempts=3 $i "cd /home/Log/;grep 'MIL' mission_2016-08-03*.log |awk -F, '{if(\$19==1370) print \$0}'|
               awk -F, '{if(\$20==0) print \$0}'>miss_info.txt"
               echo "" >&5  
 #任务执行完后在fd5中写入一个占位符,以保证这个线程执行完后,线程继续保持占位,继而维持管道中永远是50个线程数,&表示该部分命令/任务放入后台不占当前的bash,实现并行处理
        } &
done
wait                #等待父进程的子进程都执行结束后再结束父进程          
exec 5>&-           #关闭fd5的管道
exit 0

你可能感兴趣的:(Bash_Shell相关,Linux系统管理手记)