速度與妥協

速度之殤

先說明一點,這裏說的併發並不是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”

我應該是明白了,read會一次併發5個,相當於把五個進程扔到後臺執行,扔完五個會停下,因爲文件描述符裏面已經沒有行了,那五個後臺進程都執行完了之後文件描述符裏面纔會有內容

下面是無註釋的腳本,方便下次直接複製使用:

#!/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

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章