Shell多進(jìn)程執(zhí)行任務(wù)

展示代碼

#!/bin/bash

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

# 分別為 創(chuàng)建管道文件,文件操作符綁定,刪除管道文件
mkfifo testfifo
exec 1000<>testfifo
rm -rf testfifo

# 對(duì)文件操作符進(jìn)行寫(xiě)入操作。 
# 通過(guò)一個(gè)for循環(huán)寫(xiě)入10個(gè)空行,這個(gè)10就是我們要定義的后臺(tái)線程數(shù)量。
for ((n=1; n<=10; n++))
do
    echo >&1000
done

# 創(chuàng)建一個(gè)備份目錄
if [[ ! -d back ]]; then
    mkdir back
fi

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

# 遍歷URLS文件,開(kāi)始執(zhí)行下載
for ((i=1;i<=$total;i++))
do {
    # 從testfifo中讀取一行
    read -u1000
    {   
        # 增加嘗試次數(shù),最大5次
        for j in {1..5}
        do 
            # 判斷單獨(dú)進(jìn)程文件目錄是否存在,不存在則創(chuàng)建目錄
            download_dir=audio"$i"
            if [[ ! -d $download_dir ]]; then
                mkdir -p $download_dir
            fi
            echo "往目錄${download_dir}中開(kāi)始下載文件,嘗試次數(shù):${j}"
            # 讀取URLS中的一行,下載文件
            you-get -o $download_dir `sed -n "$i"p urls | tr -d '\r'`

            # 校驗(yàn)是否有異常,如果沒(méi)有異常,則跳出循環(huán),執(zhí)行外下一條,如果有異常,再次嘗試下載
            if [[ $? != 0 ]]; then
                mv $download_dir/* back
                rm -rf $download_dir
            else
                break
            fi
        done
        # 向文件操作符中寫(xiě)入一個(gè)空行
        echo >&1000
    }&
}
done

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

exec 1000>&-
exec 1000<&-

所謂多進(jìn)程,就是將一個(gè)任務(wù)劃分成多個(gè)子任務(wù)放在后臺(tái)執(zhí)行。"FIFO"是一種特殊的文件類(lèi)型,它允許獨(dú)立的進(jìn)程通訊. 一個(gè)進(jìn)程打開(kāi)FIFO文件進(jìn)行寫(xiě)操作,而另一個(gè)進(jìn)程對(duì)之進(jìn)行讀操作, 然后數(shù)據(jù)便可以如同在shell或者其它地方常見(jiàn)的的匿名管道一樣流線執(zhí)行。默認(rèn)情況下,創(chuàng)建的FIFO的模式為0666('a+rw')減去umask中設(shè)置的位。

串行、并行

串行任務(wù)

為了比較并行和串行的區(qū)別,我們先寫(xiě)個(gè)串行的腳本:

#!/bin/bash

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

執(zhí)行結(jié)果如下:

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

從結(jié)果來(lái)開(kāi),執(zhí)行完上面10次任務(wù),每次任務(wù)2秒,總耗時(shí)21秒,符合代碼的邏輯。

并行任務(wù)

先將上面的串行任務(wù)改成多線程并行任務(wù)。

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

執(zhí)行上面腳本的結(jié)果如下:

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

通常,用{}將不占處理器卻很耗時(shí)的任務(wù)放包裝一個(gè)塊,通過(guò)&放置在后臺(tái)運(yùn)行,以達(dá)到節(jié)約時(shí)間的效果。上面并行代碼,我們把10次任務(wù)全部放在后臺(tái)執(zhí)行,每個(gè)人物耗時(shí)2秒,由于并行執(zhí)行,總耗時(shí)也就是Max(任務(wù)耗時(shí))=2秒。

{
    echo $i;
    sleep 2
}&

在小任務(wù)跟前,這種方式運(yùn)用起來(lái)得心應(yīng)手,但是在任務(wù)量過(guò)大的時(shí)候,這種方式的缺點(diǎn)也就顯而易見(jiàn)了:無(wú)法控制運(yùn)行在后臺(tái)的進(jìn)程數(shù),不能就10萬(wàn)個(gè)任務(wù)就是跑10萬(wàn)個(gè)進(jìn)程吧。為了控制進(jìn)程,我們引入了管道文件操作符。

管道、文件操作符

管道

管道就像水管,有流入才會(huì)有流出,水管數(shù)水流的通道,管道是數(shù)據(jù)的通道。管道分為無(wú)名管道和有名管道。

管道類(lèi)別 命令 栗子
無(wú)名管道 常用的` `就是管道,只不過(guò)是無(wú)名的,可以直接作為兩個(gè)進(jìn)程的數(shù)據(jù)通道 `echo "hello world, I'm a test" grep "test"`
有名管道 mkfilo 可以創(chuàng)建一個(gè)管道文件 mkfiflo testfifo

管道有一個(gè)特點(diǎn),如果管道中沒(méi)有數(shù)據(jù),那么取管道數(shù)據(jù)的操作就會(huì)阻塞,直到管道內(nèi)進(jìn)入數(shù)據(jù),然后讀出后才會(huì)終止這一操作,同理,寫(xiě)入管道的操作如果沒(méi)有讀取操作,這一個(gè)動(dòng)作也會(huì)阻塞。

管道符寫(xiě)入阻塞

當(dāng)通過(guò)echo命令往fifotest管道中寫(xiě)入數(shù)據(jù)時(shí),由于沒(méi)有任何其他消費(fèi)進(jìn)程對(duì)管道操作,所以,該管道阻塞,直到再打開(kāi)一個(gè)窗口且通過(guò)cat操作該管道。


管道符寫(xiě)入讀取

同理,先操作讀取管道也會(huì)出現(xiàn)阻塞的情況。


管道符讀取阻塞

通過(guò)以上實(shí)驗(yàn),看以看到,僅僅一個(gè)管道文件似乎很難實(shí)現(xiàn)控制后臺(tái)線程數(shù),因此我們接下來(lái)簡(jiǎn)單介紹 文件操作符。

文件操作符

系統(tǒng)運(yùn)行起始,就相應(yīng)設(shè)備自動(dòng)綁定到了 三個(gè)文件操作符 分別為01 、2 對(duì)應(yīng) stdin、stdoutstderr 。在 /proc/self/fd 或者/dev/fd中可以看到這三個(gè)對(duì)應(yīng)文件:

linux fd

輸出到這三個(gè)文件的內(nèi)容都會(huì)顯示出來(lái)。只是因?yàn)轱@示器作為最常用的輸出設(shè)備而被綁定。

在Linux中,可以通過(guò)exec指令自行定義、綁定文件操作符,文件操作符一般從3~(n-1)都可以隨便使用,此處的nulimit -n的定義值。

ulimit -n

從上圖可以看出本機(jī)的n值為8192 ,所以文件操作符只能使用0-8192 ,可自行定義的就只能是3-8192。

雖然exec和source都是在父進(jìn)程中直接執(zhí)行,但exec這個(gè)與source有很大的區(qū)別,source是執(zhí)行shell腳本,而且執(zhí)行后會(huì)返回以前的shell。而exec的執(zhí)行不會(huì)返回以前的shell了,而是直接把以前登陸shell作為一個(gè)程序看待,在其上經(jīng)行復(fù)制。

exec可參考此文:《linux 下的 mkfifo、exec 命令使用

代碼分析

第3行:

  • 接受信號(hào) 2 (ctrl +C)做的操作。
  • 我們生成文件描述符并做綁定時(shí),可以用exec 1000<>testfifo來(lái)實(shí)現(xiàn),但關(guān)閉時(shí)必須分開(kāi)來(lái)寫(xiě)。
  • >讀的綁定,< 標(biāo)識(shí)寫(xiě)的綁定 <> 則標(biāo)識(shí)對(duì)文件描述符1000的所有操作,其等同于對(duì)管道文件testfifo的操作。

第6-8行:

  • 分別為 創(chuàng)建管道文件,文件操作符綁定刪除管道文件
  • 可能會(huì)有疑問(wèn),為什么不能直接使用管道文件呢?事實(shí)上,這并非多此一舉,剛才已經(jīng)說(shuō)明了管道文件的一個(gè)重要特性了,那就是讀寫(xiě)必須同時(shí)存在,缺少某一種操作,另一種操作就是阻塞,而綁定文件操作符正好解決了這個(gè)問(wèn)題。

第12-15行:

  • 對(duì)文件操作符進(jìn)行寫(xiě)入操作。 通過(guò)一個(gè) for 循環(huán)寫(xiě)入 10 個(gè)空行,這個(gè) 10 就是我們要定義的后臺(tái)線程數(shù)量
  • 為什么寫(xiě)入空行而不是 10 個(gè)字符呢?這是因?yàn)?,管道文件的讀取是以為單位的。
  • 當(dāng)我們?cè)噲D用 read 讀取管道中的一個(gè)字符時(shí),結(jié)果是不成功的,上面的例子已經(jīng)證實(shí)了使用cat是可以讀取的。

第32-61行:

  • 遍歷urls的總行數(shù),循環(huán)處理url
  • 25-29行是讀取urls文件的總行數(shù)的邏輯(看開(kāi)篇代碼)。
  • 這里我們有$total個(gè)任務(wù)($total是變量,是讀取的urls的總行數(shù),值大于0),我們需要保證后臺(tái)只有10個(gè)進(jìn)程在同步運(yùn)行(當(dāng)然這段代碼有點(diǎn)小遺憾,就是未能根據(jù)總行數(shù)決定用多少個(gè)進(jìn)程,加入總行數(shù)小于10,但我們創(chuàng)建了10行空字符串,但這并不影響我們的測(cè)試) 。
  • read -u1000 的作用是:讀取一次管道中的一行,在這兒就是讀取一個(gè)空行。
  • 減少操作附中的一個(gè)空行之后,執(zhí)行一次任務(wù)(當(dāng)然是放到后臺(tái)執(zhí)行),需要注意的是,這個(gè)任務(wù)在后臺(tái)執(zhí)行結(jié)束以后會(huì)向文件操作符中寫(xiě)入一個(gè)空行,這就是重點(diǎn)所在,如果我們不在某種情況某種時(shí)刻向操作符中寫(xiě)入空行,那么結(jié)果就是:在后臺(tái)放入10個(gè)任務(wù)之后,由于操作符中沒(méi)有可讀取的空行,導(dǎo)致read -u1000這兒始終停頓。
  • 第38-56行,處理自己的業(yè)務(wù),這里面是通過(guò)you-get下載url中的圖片、語(yǔ)音,如果下載失敗,最多嘗試5次。關(guān)于you-get參考這篇文章《You-Get:支持 80 多個(gè)網(wǎng)站的命令行多媒體下載器》了解其更多。

第64-69行:

  • 等待所有進(jìn)程執(zhí)行結(jié)束。
  • exec 1000>&-exec 1000<&-是關(guān)閉 fd1000。

該文首發(fā)《虛懷若谷》個(gè)人博客。轉(zhuǎn)發(fā)請(qǐng)注明原創(chuàng)作者:若谷

古之善為道者,微妙玄通,深不可識(shí)。夫唯不可識(shí),故強(qiáng)為之容:

豫兮若冬涉川,猶兮若畏四鄰,儼兮其若客,渙兮若冰之釋?zhuān)刭馄淙魳?,曠兮其若谷,混兮其若濁?/p>

孰能濁以靜之徐清?孰能安以動(dòng)之徐生?

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容