转 Shell脚本中的多任务并发执行
感谢yttitan
https://blog.51cto.com/yttitan/2409618
正常情况下,Shell脚本中的命令是串行执行的,当一条命令执行完才会执行接下来的命令。比如下面这段代码:
#!/bin/bash
for i in {1..10};do
echo $i
done
echo "END"
执行结果:
1
2
3
4
5
6
7
8
9
10
END
可以看到,循环体中的“echo $i”命令是串行执行的。但是如果所执行的命令耗时比较长,这就会导致整个程序的执行时间非常长,甚至可能导致程序执行时卡在那里,长时间失去响应。
比如我们需要完成这样一个任务:编写一个脚本,扫描192.168.80.0/24网络里,当前在线的主机有哪些,能ping通就认为在线。
要完成这个任务,编写脚本并不复杂,下面是写好的代码:
#!/bin/bash
for i in {1..254};do
ip="192.168.80.$i"
ping -c 2 $ip &> /dev/null && echo $ip is up
done
这里对脚本中使用的ping命令稍作说明。Linux中的ping命令在执行后会连续不断地发包,因而脚本中的ping命令使用了“-c”选项,指定只发2次包,如果能收到响应,就认为目标主机在线。
这个脚本在逻辑上并没有问题,但是在执行后由于要对网络中的254个IP地址轮流执行ping命令,耗时非常长,而且此时的脚本无法使用Ctrl+C强制终止,只能使用Ctrl+Z转入后台,然后再用kill命令强制结束进程。
[root@localhost ~]# bash ping.sh
192.168.80.1 is up
192.168.80.2 is up
^C
^Z
[1]+ 已停止 bash ping.sh
[root@localhost ~]# jobs -l #查看后台工作任务
[1]+ 101100 停止 bash ping.sh
[root@localhost ~]# kill -9 101100 #强制结束进程
[root@localhost ~]#
[1]+ 已杀死 bash ping.sh
实际上在这个脚本中所循环执行的ping命令之间并没有依赖关系,也就是说不必非要等到“ping 192.168.80.1”结束之后才能接着执行“ping 192.168.80.2”,所有的这些ping命令完全可以并发执行。
如果是使用Python,那么可以借助于多线程技术来实现命令的并发执行,而Shell不支持多线程,因而只能采用多进程的方式。具体的实现方法很简单,就是在要并发执行的命令后面加上“&”,将其转入后台执行,这样就可以在执行完一条命令之后,不必等待其执行结束,就立即转去执行下一条命令。
我们还是以之前的代码为例,在循环体中的echo命令之后加上“&”:
#!/bin/bash
for i in {1..10};do
echo $i &
done
echo "END"
执行结果:
[root@localhost ~]# bash test.sh
END
[root@localhost ~]# 1
2
3
6
7
4
8
9
10
5
可以看到,在并发执行时不能保证命令的执行顺序,而且本应在整个循环执行结束之后再执行的echo "END"命令,却在程序一开始就被执行了。所以在并发执行时,我们通常都需要保证在循环体中的所有命令都执行完后再向后执行接下来的命令,这时就可以使用 wait命令来实现。在Shell中使用wait命令,相当于其它高级语言里的多线程同步。
下面对代码进行改进,增加wait命令:
#!/bin/bash
for i in {1..10};do
echo $i &
done
wait
echo "END"
这样执行结果就正常了:
[root@localhost ~]# bash test3.sh
6
7
2
3
4
8
9
10
5
1
END
了解了程序并发执行的原理之后,我们对ping脚本也同样进行改进:
#!/bin/bash
for i in {1..254};do
ip="192.168.80.$i"
ping -c 2 $ip &> /dev/null && echo $ip is up &
done
wait
此时脚本的执行速度将大大提高:
[root@localhost ~]# bash ping.sh
192.168.80.10 is up
192.168.80.20 is up
192.168.80.2 is up
192.168.80.1 is up
192.168.80.135 is up
因而当要循环执行的命令之间没有依赖关系时,完全可以采用并发执行的方式,这样可以大幅提高代码执行效率。当然并发执行也有缺陷,就是当需要并行执行的命令数量特别多,特别是所执行的命令占用的系统资源非常多时,可能会将整个系统的资源全部耗尽,影响其它程序的运行,因而还可以借助其它技术来限制并发执行的进程数量,由于比较复杂,本文就不做介绍了。