默认的情况下,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

————————————————
版权声明:本文为CSDN博主「白金牧场」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34409701/article/details/52488964


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

什么Shell也能并行化知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。