为了方便理解,一步步的来
首先先看一下串行的:
#! /bin/bash ST=$(date +%s) for i in $(seq 1 10) do echo $i sleep 1 # 模拟程序、命令 done ET=$(date +%s) TIME=$(( ${ET} - ${ST} )) echo "time: ${TIME}"
输出结果:
1 2 3 4 5 6 7 8 9 10 time: 10
这就最原始的进程运行模拟,串行方式,无法有效利用计算机的资源,很浪费切耗时。
我们可以把进程放入后台运行,这样的可以达到并发执行的效果:
#! /bin/bash ST=$(date +%s) for i in $(seq 1 10) do { echo $i sleep 1 # 模拟程序、命令 }& # 把循环体放入后台运行,相当于是起一个独立的线程,在此处的作用就是相当于起来10个并发 done wait # wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再往下执行,通常可以和&搭配使用 ET=$(date +%s) TIME=$(( ${ET} - ${ST} )) echo "time: ${TIME}"
执行的结果:
1 2 3 4 5 6 7 8 9 10 time: 1
有次可见,执行后时间由原来的10缩短到了1秒,这是很大的改进
但是有个问题就是,这里我的总任务才是10个,那如果我的任务是1000个,那么计算机资源就不足了,就容易造成宕机。
所以需要控制并发数据量,之前我想过很多方法,比如切割任务,和控制循环的时间等等,都不太优雅,查阅资料,找到一个可行方案:使用管道和文件描述符。
管道:
因为我对这方面也是新知识,所以一开始就看代码,有点懵,先看点理论知识。
首先说是管道,有无名管道和有名管道
无名管道,在日常使用频率超高,比如:
ps -ef | grep java ls | wc -l
这里面的“|”就是管道,它将前一个命令的结果输出到后一个进程中,作为两个进程的数据通道,不过他是无名的。
使用mkfifo命令创建的管道即为有名管道,例如,mkfifo pipefile, pipefile即为有名管道
有名管道有一个显著的特点:
如果管道里没有数据,那么去取管道数据时,程序会阻塞住,直到管道内进入数据,然后读取才会终止这个操作,反之,管道在执行写入操作时,如果没有读取操作,同样会阻塞
由此可以得到的有用内容就是:利用有名管道的特性就可以实现一个队列的控制。
验证就不做了,网上验证资料很多。
文件描述符:
这个就不过多说了,网上资料也很多。
但是值得注意的是,文件描述符的值不能乱取,取值范围是3-(ulimit -n)-1
ulinit -n是 系统的open files
默认一般就是1024, 直接输入ulimit -n就可以查看
所以代码
#! /bin/bash ST=$(date +%s) [ -e /tmp/fd1 ] || mkfifo /tmp/fd1 # 创建有名管道 exec 5<>/tmp/fd1 # 创建文件描述符,以可读(<)可写(>)的方式关联管道文件,文件描述符5拥有有名管道文件的所有特性 rm -rf /tmp/fd1 # 文件描述符关联后拥有管道的所有特性,所有删除管道 NUM=$1 # 获取输入的并发数 for (( i=1;i<=${NUM};i++ )) do echo >&5 # &5表示引用文件描述符5,往里面放置一个令牌 done for i in $(seq 1 100) do read -u5 { echo $i sleep 1 # 模拟程序、命令 echo >&5 # 执行完把令牌放回管道 }& # 把循环体放入后台运行,相当于是起一个独立的线程,在此处的作用就是相当于起来10个并发 done wait # wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再往下执行,通常可以和&搭配使用 ET=$(date +%s) TIME=$(( ${ET} - ${ST} )) echo "time: ${TIME}"
运行:
mshing@remtor:~/code$ ./Concurrent.sh 20 1 2 3 4 5 6 ...... 95 96 97 98 99 100 time: 5
代码分析
举个例子吧,就拿以前我们学车来说,我们学车的时候就是车少人多,假如有20人练车,但是只有5辆教练车,车钥匙放在教练那里,每个人去练车要去和教练取钥匙练完放回教练那里。那么运行就是每次只能5人同时练习,等有人把车钥匙放回教练那里,下一个才能得到钥匙去练车。只到20个人练完。任务就算完成
该并发是可用的可行的
把里面的循环范围和命令改一下就可批量并发执行了
最开始产生的需求源于自己的工作,因为工作中需要对数据库的700多张表同时truncate,然是我们的工具很不好使用,而且工具里面操作的也是串行的,有时候truncate表重建表速度更快一些,但是这样是不规范的操作,我们不应该去动表结构。所以产生了使用shell去操作数据库的想法,所以就来做个小脚本。