当我们工作中遇到一些批量分发、批量执行场景时,写的一些shell脚本都是单线程任务,当然这些量级不大的时候,看不出劣势。
举个例子:现在需要通过跳板机,分发一个文件到10台服务器,每台服务器传输需要1s时间,10台服务器传输完就是10s。看下面脚本,这里通过sleep命令模拟传输耗时。
#!/bin/bash #Author: www.amd5.cn #Description: 模拟分发文件到10台服务器 startTime=`date +%s` for i in `seq 10`;do echo $i sleep 1 done stopTime=`date +%s` echo "耗时: $[$stopTime - $startTime] s"
这个执行结果不用思考,就是10s。
# bash test.sh 1 2 3 4 5 6 7 8 9 10 耗时: 10 s
如果是500台服务器或者1000台服务器,那命令执行以后就慢慢等吧。那如果文件比较大,一个需要30s呢?1000 * 30 =30000 s 基本上就是8个多小时。早上上班执行,等到下班刚刚好,如果你的脚本是前台运行,中间突然网络波动,导致远程断开了,那就厕所哭去吧。
其实文件分发大不了就是等嘛,但是如果是批量检查服务器状态,或者批量启动服务,服务器数量一大,那劣势就特别明显了。
那么有没有办法提高效率呢?答案是肯定的,利用多线程,本来需要10s完成的任务,我使用了5个线程,那么跑下来基本也就2s左右。shell不像 python、go语言,他们本身就有多线程模块。
shell只能利用管道和文件描述符实现多线程任务,直接脚本测试刚才的分发文件。
#!/bin/bash #Author: www.amd5.cn #Description: 模拟多线程分发文件到10台服务器 startTime=`date +%s` tmpfifo="/tmp/$$.fifo" [ -e $tmpfifo ] || mkfifo $tmpfifo exec 3<>$tmpfifo rm -rf $tmpfifo for i in `seq 5`;do echo >&3 done for i in `seq 10`;do read -u3 { sleep 1 echo $i echo >&3 }& done wait stopTime=`date +%s` echo "耗时: $[$stopTime - $startTime] s" exec 3<&- exec 3>&-
执行结果如下图:
如果是10个线程,结果就是1s,如果是6、7、8、9个线程,整个任务执行完也是2s,所以需要根据实际的情况设置线程数,也并不是线程越多越好,整个要看本身执行脚本的服务器CPU核数以及其它资源情况。
下面对脚本语句进行分析:
#!/bin/bash #Author: www.amd5.cn #Description: 模拟多线程分发文件到10台服务器 startTime=`date +%s` tmpfifo="/tmp/$$.fifo" #判断有名管道是否存在 创建有名管道文件 [ -e $tmpfifo ] || mkfifo $tmpfifo #创建文件描述符 exec 3<>$tmpfifo,(可读(<)可写(>))创建文件描述符3关联管道文件,这时候3这个文件描述符就拥有了管道的所有特性。 #还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符: &3可以执行n次echo >&3 往管道里放入n把钥匙。并发n exec 3<>$tmpfifo #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,留下文件描述符来用就可以了。 rm -rf $tmpfifo for i in `seq 5`;do #&3代表引用文件描述符3,这条命令代表往管道里面放入了一个"令牌",`seq 5` 循环5次,也就是5个令牌,即5个线程 echo >&3 done for i in `seq 10`;do #代表从管道中读取一个令牌 read -u3 { sleep 1 echo $i #代表我这一次命令执行到最后,把令牌放回管道 echo >&3 }& #用{}把循环体括起来,后加一个&符号,代表每次循环都把命令放入后台运行 done #wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再往下执行 wait stopTime=`date +%s` echo "耗时: $[$stopTime - $startTime] s" #关闭文件描述符的读 exec 3<&- #关闭文件描述符的写 exec 3>&-
其实主要就是mkfifo和exec两个命令的使用。
exec命令
用于调用并执行指令的命令。exec命令通常用在shell脚本程序中,可以调用其他的命令。如果在当前终端中使用命令,则当指定的命令执行完毕后会立即退出终端。
Linux每打开一个shell就会打开默认的三个文件描述符描0,1,2,分别代表标准输入,标准输出和标准错误输出。需要的时候我们可以使用exec命令指定一个大于3的数字作为文件。
1、exec 3</tmp/1.txt #以“只读方式”打开/tmp/1.txt,文件描述符对应为3
2、exec 3>/tmp/1.txt #以“只写方式”打开/tmp/1.txt,文件描述符对应为3
3、exec 3<>/tmp/1.txt #以“读写方式”打开/tmp/1.txt,文件描述符对应为3
4、exec 3<&- #关闭文件描述符3
转载请注明:阿汤博客->Linux Shell脚本如何实现多线程 http://www.amd5.cn/atang_4575.html