速度之殇
先说明一点,这里说的并发并不是web里面的并发访问,而是脚本里面的并发,脚本里面有并发?是的,脚本里面是有并发的,不过,我感觉称之为“后台控制”更为贴切一些。说到这里,你可能依然感觉困惑,没有关系,慢慢来!下面先用for循环举一个“栗子”:
上面这个脚本是对192.168.80.0作一个ping探测,探测当前有哪些主机在线,当然这个脚本还有很多改动的余地,不过这都不是重点,这个脚本最大的问题是执行的速度太慢了,脚本太慢的原因是因为里面的ping命令太慢了,每一个ping都要耗时1秒,我们要探测200多个地址,整个脚本执行完成就要三分钟以上,这里的ping是线性的,所谓的线性的就是第一个地址ping完成之后然后才开始ping下一个地址,写到这里不知道你是否能和之前学过的某些知识联系起来?反正我写笔记的目的之下就是如此,我是想到了一个之前学过的知识,linux启动之后用户空间启动的第一个进程是init程序,centos5和centos6都称用户空间的第一个程序为init,但是实际上centos5的init进程是名副其实的init进程,而centos6上的init进程是有名无实(只是保留了init的名字而已,实际上是upstart),那么centos5和centos6上的init有何区别?centos5上的init进程就像上面的脚本,启动完一个进程之后再启动下一个进程,一个接着一个,就像排队似的,而centos6的init启动进程就是多个进程同时启动,同时启动的速度就比较快,我们姑且就把这种同时启动多个任务的机制叫做并发,centos6比centos5的开机速度快,init程序的并发机制功不可没!那么我们如何改进上面的脚本呢?根据init带来的启示,即使造成脚本速度慢的原因是因为ping命令运行比较慢,那么我们当每个ping没有执行完成的时候就将其送到后台执行,这样多个ping进程可以同时执行,可以大大加快ping的速度!改进也很简单,把ping探测通过&符号送往后台不就可以嘛!说干就干,改进后的脚本如下:
注:与wait相关的知识我会再用一篇笔记来写,这里先不多做解释。
经过修改,当我们执行的时候,整个脚本几乎会瞬间完成,爽!美中不足的就是,脚本向外输入文本时并不是按照顺序来的,我们可以通过sort命令进行排序!
脚本执行时的情况如下:
[root@linuxprobe tmp]# ./ping.sh 192.168.80.230 is down. 192.168.80.239 is up. 192.168.80.220 is down. 192.168.80.241 is down. ……
可通过sort命令进行改进,就是执行时进行排序,如下:
[root@linuxprobe tmp]# ./ping.sh | sort -n -t. -k 4 #-t指定分隔符,-k指定要排序的列,-n从小到大排序 192.168.80.230 is down. 192.168.80.231 is down. 192.168.80.232 is down. 192.168.80.233 is down. 192.168.80.234 is down. ……
小结:通过执行结果我们看到了脚本的速度上来了,并且也没有看出什么隐患,但情况绝非像我们看到的看么美好!
脚本的中庸之道
现在的社会如果只知道存钱不懂得理财的话,一个普通人很难通过存钱使自己变得富有,也会有一些人希望通过一些别的途径让自己变得富有,比如股票、×××、×××等等,也的确有人通过这些方式使自己迅速变更富有。就像我的上一辈人一样,虽然日子过的踏实,但是在人生的路途上实在是走的太过缓慢,一辈子也没有过走多远;一个原本只知道存钱的人突然获得了大量的财富,不再被生活所累,问题也会随之而来,他真的能守住这么多的财富吗?
我们第一个脚本就像一个老老实实存钱的人,踏实,缓慢;第二个脚本就是给这样一个老老实实的人一个大大的运气,让其中了猛然间获得了大量的财富,但问题随之浮现,脚本在执行时有些“语无伦次”,顺序错乱了,尽管我们通过外在的表象(sort)强制给脚本排序,但是这终归是“治标不治本”。
第二个脚本怎么了?
当大量的ping进程被放置到了后台,系统资源就有些不堪重负了,所以导致了语无伦次的情况,这还仅仅是200多个进程,如果更多的话,脚本的执行就不能顺利的进行了。很多书讲到最后都是中庸之道,脚本的执行也不例外。
怎样让脚本变得“中庸”呢?
人想变得中庸非常不容易,中庸并不是我们自认为的不上不下,不温不火,中庸的真正的含义应该是:合适的极致,增之一分太长,减之一份太短!脚本想要变得中庸也不容易,我们要从文件标识符讲起。
那么什么是文件标识符?
当一个程序使用一个文件的时候,要从硬盘当中把文件内容调用到内存,在用户空间里会用一个标识符来指向文件所在的内存空间,当某个进程正在使用某个文件的时候,假如我们通过rm -rf将其删除的话,仅仅表面上被删除了,其实在内存空间里面还保留有此文件的信息,我们还可以通过cp命令恢复,如果我们我们释放了这个文件标识符之后文件就再也找不回来了,因为释放文件标识符,就意味着释放了内存空间。下面举个例子:
[root@linuxprobe tmp]# echo 123 >> test.txt #在/tmp里面生成一个文件,内容是123 [root@linuxprobe tmp]# exec 9<> test.txt #在当前进程下给此文件分配文件标识符 [root@linuxprobe tmp]# ll /proc/$$/fd #分配完文件标识符之后,在当前进程下我们就看到了此文件,$$代表当前进程 lrwx------ 1 root root 64 7月 12 07:42 255 -> /dev/pts/0 lrwx------ 1 root root 64 7月 12 07:42 9 -> /tmp/test.txt [root@linuxprobe tmp]# cat test.txt #我们也可以查看文件标识符,效果和查看文件是一样的,向文件标识符里面输入内容- 123 就相当于将内容输入到文件里面去. [root@linuxprobe tmp]# rm -rf test.txt #现在我们把此文件删除 [root@linuxprobe tmp]# ls [root@linuxprobe tmp]# ll /proc/$$/fd #却发现进程里面还有此文件的文件标识符,意味着内存空间里面的的数据没有删除 lrwx------ 1 root root 64 7月 12 07:42 255 -> /dev/pts/0 lrwx------ 1 root root 64 7月 12 07:42 9 -> /tmp/test.txt (deleted) [root@linuxprobe tmp]# cp /proc/$$/fd/9 /tmp/test.txt #通过cp命令就可以恢复 [root@linuxprobe tmp]# cat /tmp/test.txt 123 [root@linuxprobe tmp]# rm -rf test.txt #我们再删除一次,这次我们把文件标识符也给它删除 [root@linuxprobe tmp]# exec 9<&- #此命令就是删除文件标识符的命令 [root@linuxprobe tmp]# ll /proc/$$/fd #发现当前进程里面没有此文件了,再也恢复不回来了 lrwx------ 1 root root 64 7月 12 07:42 255 -> /dev/pts/0
谈完文件标识符 ,这仅仅第一步,然后我们还要再谈谈管道.
linux当中的重定向和管道都是用来控制输出的,不同的是,重定向是把内容控制输出到文件当中,而管道是把内容控制输出到程序当中。我们平时使用的管道是匿名管道,匿名管道都没有名字,它也是一个文件,其实还有一种管道是命名管道,命名管道就是有名字的管道,无论是匿名管道还是命名管道都有一个共同的特点就是里面的内容只能用一次,用过之后就没有了,我们可以通过一个命名管道来试验一下:
[root@linuxprobe tmp]# mkfifo test.fifo #创建一个命名管道文件 [root@linuxprobe tmp]# file test.fifo test.fifo: fifo (named pipe) [root@linuxprobe tmp]# cat test.fifo #刚开始用cat查看一下,里面什么内容都没有 #此时,我们通过另一个终端向这个文件里面输出了内容,这边会立刻显示 [root@linuxprobe tmp]# cat test.fifo #当我们再次查看的时候,发现内容已经没有了
我们知道了这两个知识点,再去学习脚本的中庸之道就轻松一些了.
/bin/bash thread=5 #定义一个变量thread,这里面的5的意思是期望一次仅并发5个进程 tmp_fifofile=/tmp/$$.fifo #再定义一个变量tmp_fifofile,这个变量指的是/tmp/$$.fifo这个文件,这个文件没有创建 mkfifo $tmp_fifofile #通过变量创建这个命名管道文件 exec 8<> $tmp_fifofile #然后给此文件分配文件描述符8,完成这一步之后其实这个文件已经被加载到内存了 rm $tmp_fifofile #然后把这个文件再给删除,删除了之后,内存里面的内容还在,通过描述符还可以读到 for i in `seq $thread` #通过for循环给i先后赋值1,2,3,4,5,就是增加五行内容 do echo >&8 #每读到一个数,就向文件描述符里面插入一行,一共插入了五行,&8就是指文件描述符8 done for i in {1..254} do read –u 8 #读8这个文件描述符,读到此描述符里面有内容才会循环,里面一共就有五行内容, { #每次读一行,每读到一行就少一行,五次完了之后此循环就再执行了 ip=192.168.80.$i ping –c1 –W1 $ip &>/dev/null if [ $? –wq 0 ];then echo “$ip is up” else echo “$ip is down” fi echo >&8 #每用掉一行,就向文件描述符里面插入一行 }& done wait exec 8>&- #最后把此文件描述符给干掉 echo “finish”
我应该是明白了,read会一次并发5个,相当于把五个进程扔到后台执行,扔完五个会停下,因为文件描述符里面已经没有行了,那五个后台进程都执行完了之后文件描述符里面才会有内容
下面是无注释的脚本,方便下次直接复制使用: