Linux Shell脚本如何实现多线程

2020年10月13日16:56:32 发表评论 3,173 ℃

当我们工作中遇到一些批量分发、批量执行场景时,写的一些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>&-

执行结果如下图:

Linux Shell脚本如何实现多线程

如果是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

【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: