天天看點

Shell多程序執行任務展示代碼串行、并行管道、檔案操作符代碼分析

展示代碼

#!/bin/bash

trap "exec 1000>&-;exec 1000<&-;exit 0" 2

# 分别為 建立管道檔案,檔案操作符綁定,删除管道檔案
mkfifo testfifo
exec 1000<>testfifo
rm -rf testfifo

# 對檔案操作符進行寫入操作。 
# 通過一個for循環寫入10個空行,這個10就是我們要定義的背景線程數量。
for ((n=1; n<=10; n++))
do
    echo >&1000
done

# 建立一個備份目錄
if [[ ! -d back ]]; then
    mkdir back
fi

# 開始時間記錄
start=`date "+%s"`
# 擷取URL總數,如果總數為0,直接退出
total=`cat urls | wc -l`
if [[ $total == 0 ]]; then
    echo 'urls總數為空'
    exit 0
fi 

# 周遊URLS檔案,開始執行下載下傳
for ((i=1;i<=$total;i++))
do {
    # 從testfifo中讀取一行
    read -u1000
    {    
        # 增加嘗試次數,最大5次
        for j in {1..5}
        do 
            # 判斷單獨程序檔案目錄是否存在,不存在則建立目錄
            download_dir=audio"$i"
            if [[ ! -d $download_dir ]]; then
                mkdir -p $download_dir
            fi
            echo "往目錄${download_dir}中開始下載下傳檔案,嘗試次數:${j}"
            # 讀取URLS中的一行,下載下傳檔案
            you-get -o $download_dir `sed -n "$i"p urls | tr -d '\r'`

            # 校驗是否有異常,如果沒有異常,則跳出循環,執行外下一條,如果有異常,再次嘗試下載下傳
            if [[ $? != 0 ]]; then
                mv $download_dir/* back
                rm -rf $download_dir
            else
                break
            fi
        done
        # 向檔案操作符中寫入一個空行
        echo >&1000
    }&
}
done

# 等待所有任務完成
wait
end=`date "+%s"`
echo "time: `expr $end - $start`"

exec 1000>&-
exec 1000<&-           

所謂多程序,就是将一個任務劃分成多個子任務放在背景執行。"FIFO"是一種特殊的檔案類型,它允許獨立的程序通訊. 一個程序打開FIFO檔案進行寫操作,而另一個程序對之進行讀操作, 然後資料便可以如同在shell或者其它地方常見的的匿名管道一樣流線執行。預設情況下,建立的FIFO的模式為0666('a+rw')減去umask中設定的位。

串行、并行

串行任務

為了比較并行和串行的差別,我們先寫個串行的腳本:

#!/bin/bash

start=`date "+%s"`
for i in {1..10}
do
    echo $i;
    sleep 2
done
end=`date "+%s"`
echo "Time: `expr $end - $start`"           

執行結果如下:

$ sh series.sh 
1
2
3
4
5
6
7
8
9
10
Time: 21            

從結果來開,執行完上面10次任務,每次任務2秒,總耗時21秒,符合代碼的邏輯。

并行任務

先将上面的串行任務改成多線程并行任務。

#!/bin/bash

start=`date "+%s"`
for i in {1..10}
do 
    {
        echo $i;
        sleep 2
    }&
done
wait
end=`date "+%s"`
echo "Time: `expr $end - $start`"           

執行上面腳本的結果如下:

$ sh paralle.sh 
1
2
3
4
5
6
7
8
9
10
Time: 2           

通常,用

{}

将不占處理器卻很耗時的任務放包裝一個塊,通過

&

放置在背景運作,以達到節約時間的效果。上面并行代碼,我們把10次任務全部放在背景執行,每個人物耗時2秒,由于并行執行,總耗時也就是Max(任務耗時)=2秒。

{
    echo $i;
    sleep 2
}&           

在小任務跟前,這種方式運用起來得心應手,但是在任務量過大的時候,這種方式的缺點也就顯而易見了:無法控制運作在背景的程序數,不能就10萬個任務就是跑10萬個程序吧。為了控制程序,我們引入了

管道

檔案操作符

管道、檔案操作符

管道

管道就像水管,有流入才會有流出,水管數水流的通道,管道是資料的通道。管道分為無名管道和有名管道。

管道類别 指令 栗子
無名管道 常用的` `就是管道,隻不過是無名的,可以直接作為兩個程序的資料通道 `echo "hello world, I'm a test" grep "test"`
有名管道

mkfilo

可以建立一個管道檔案

mkfiflo testfifo

管道有一個特點,如果管道中沒有資料,那麼取管道資料的操作就會阻塞,直到管道内進入資料,然後讀出後才會終止這一操作,同理,寫入管道的操作如果沒有讀取操作,這一個動作也會阻塞。

當通過echo指令往fifotest管道中寫入資料時,由于沒有任何其他消費程序對管道操作,是以,該管道阻塞,直到再打開一個視窗且通過cat操作該管道。

同理,先操作讀取管道也會出現阻塞的情況。

通過以上實驗,看以看到,僅僅一個管道檔案似乎很難實作控制背景線程數,是以我們接下來簡單介紹

檔案操作符

檔案操作符

系統運作起始,就相應裝置自動綁定到了 三個檔案操作符 分别為

1

2

對應

stdin

stdout

stderr

。在

/proc/self/fd

或者

/dev/fd

中可以看到這三個對應檔案:

輸出到這三個檔案的内容都會顯示出來。隻是因為顯示器作為最常用的輸出裝置而被綁定。

在Linux中,可以通過

exec

指令自行定義、綁定檔案操作符,檔案操作符一般從3~(n-1)都可以随便使用,此處的n為

ulimit -n

的定義值。

從上圖可以看出本機的

n

值為

8192

,是以檔案操作符隻能使用

0-8192

,可自行定義的就隻能是

3-8192

雖然exec和source都是在父程序中直接執行,但exec這個與source有很大的差別,source是執行shell腳本,而且執行後會傳回以前的shell。而exec的執行不會傳回以前的shell了,而是直接把以前登陸shell作為一個程式看待,在其上經行複制。

exec可參考此文:《

linux 下的 mkfifo、exec 指令使用

代碼分析

第3行:

  • 接受信号 2 (ctrl +C)做的操作。
  • 我們生成檔案描述符并做綁定時,可以用

    exec 1000<>testfifo

    來實作,但關閉時必須分開來寫。
  • >

    讀的綁定,

    <

    辨別寫的綁定

    <>

    則辨別對檔案描述符1000的所有操作,其等同于對管道檔案testfifo的操作。

第6-8行:  

  • 分别為

    建立管道檔案

    檔案操作符綁定

    删除管道檔案

  • 可能會有疑問,為什麼不能直接使用管道檔案呢?事實上,這并非多此一舉,剛才已經說明了管道檔案的一個重要特性了,那就是讀寫必須同時存在,缺少某一種操作,另一種操作就是阻塞,而綁定檔案操作符正好解決了這個問題。

    第12-15行:

  • 對檔案操作符進行寫入操作。 通過一個 for 循環寫入 10 個空行,這個 10 就是我們要定義的背景線程數量。
  • 為什麼寫入空行而不是 10 個字元呢?這是因為,管道檔案的讀取是以

    為機關的。
  • 當我們試圖用 read 讀取管道中的一個字元時,結果是不成功的,上面的例子已經證明了使用cat是可以讀取的。

第32-61行:

  • 周遊urls的總行數,循環處理url
  • 25-29行是讀取urls檔案的總行數的邏輯(看開篇代碼)。
  • 這裡我們有

    $total

    個任務($total是變量,是讀取的urls的總行數,值大于0),我們需要保證背景隻有10個程序在同步運作(當然這段代碼有點小遺憾,就是未能根據總行數決定用多少個程序,加入總行數小于10,但我們建立了10行空字元串,但這并不影響我們的測試) 。
  • read -u1000

    的作用是:讀取一次管道中的一行,在這兒就是讀取一個空行。
  • 減少操作附中的一個空行之後,執行一次任務(當然是放到背景執行),需要注意的是,這個任務在背景執行結束以後會向檔案操作符中寫入一個空行,這就是重點所在,如果我們不在某種情況某種時刻向操作符中寫入空行,那麼結果就是:在背景放入10個任務之後,由于操作符中沒有可讀取的空行,導緻

    read -u1000

    這兒始終停頓。
  • 第38-56行,處理自己的業務,這裡面是通過

    you-get

    下載下傳url中的圖檔、語音,如果下載下傳失敗,最多嘗試5次。關于

    you-get

    參考這篇文章《 You-Get:支援 80 多個網站的指令行多媒體下載下傳器 》了解其更多。

第64-69行:

  • 等待所有程序執行結束。
  • exec 1000>&-

    exec 1000<&-

    是關閉

    fd1000

該文首發

《虛懷若谷》

個人部落格,轉載前請務必署名,轉載請标明出處。

古之善為道者,微妙玄通,深不可識。夫唯不可識,故強為之容:

豫兮若冬涉川,猶兮若畏四鄰,俨兮其若客,渙兮若冰之釋,敦兮其若樸,曠兮其若谷,混兮其若濁。

孰能濁以靜之徐清?孰能安以動之徐生?

保此道不欲盈。夫唯不盈,故能敝而新成。

請關注我的微信公衆号:下雨就像彈鋼琴,Thanks(・ω・)ノ