速度之殇
先說明一點,這裡說的并發并不是web裡面的并發通路,而是腳本裡面的并發,腳本裡面有并發?是的,腳本裡面是有并發的,不過,我感覺稱之為“背景控制”更為貼切一些。說到這裡,你可能依然感覺困惑,沒有關系,慢慢來!下面先用for循環舉一個“栗子”:
#!/bin/bash
#Author:zhanghe
for i in {1..255};do
ping -c1 -W1 192.168.80.$i &>/dev/null #可用arping替換
if [ $? -eq 0 ];then
echo "192.168.80.$i is up."
else
echo "192.168.80.$i is down."
fi
done
上面這個腳本是對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探測通過&符号送往背景不就可以嘛!說幹就幹,改進後的腳本如下:
[root@linuxprobe tmp]#vim ping.sh
#!/bin/bash
#Author:zhanghe
for i in {1..255};do
{ #看就是加了一個大括号,另一半在倒數第四行呢!
ping -c1 -W1 192.168.80.$i &>/dev/null #可用arping替換
if [ $? -eq 0 ];then
echo "192.168.80.$i is up."
else
echo "192.168.80.$i is down."
fi
}&#這裡,然後在大括号後面又加了一個&,&符号的目的是将大括号括起來的内容送往背景。
done
wait#wait的意思是等前面for循環裡面所有的内容運作完畢之後才能向下執行
echo "finish!"#完成之後要“吱”一聲!
注:與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”
#!/bin/bash
#author zhanghe
thread=5
FIFO=/tmp/test
mkfifo $FIFO
exec 8<>$FIFO
rm -rf $FIFO
for i in `seq $thread`;do
echo >&8
done
for i in `seq 1 100`;do
{
read -u 8
ip=192.168.80.
ping -c1 -W1 $ip$i &>/dev/null
if [ $? -eq 0 ];then
echo "$ip$i is up."
else
echo "$ip$i is down."
fi
echo >&8
}&
done
wait
echo finish