展示代碼
#!/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"` |
有名管道 | 可以建立一個管道檔案 | |
管道有一個特點,如果管道中沒有資料,那麼取管道資料的操作就會阻塞,直到管道内進入資料,然後讀出後才會終止這一操作,同理,寫入管道的操作如果沒有讀取操作,這一個動作也會阻塞。
當通過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是變量,是讀取的urls的總行數,值大于0),我們需要保證背景隻有10個程序在同步運作(當然這段代碼有點小遺憾,就是未能根據總行數決定用多少個程序,加入總行數小于10,但我們建立了10行空字元串,但這并不影響我們的測試) 。$total
-
的作用是:讀取一次管道中的一行,在這兒就是讀取一個空行。read -u1000
- 減少操作附中的一個空行之後,執行一次任務(當然是放到背景執行),需要注意的是,這個任務在背景執行結束以後會向檔案操作符中寫入一個空行,這就是重點所在,如果我們不在某種情況某種時刻向操作符中寫入空行,那麼結果就是:在背景放入10個任務之後,由于操作符中沒有可讀取的空行,導緻
這兒始終停頓。read -u1000
- 第38-56行,處理自己的業務,這裡面是通過
下載下傳url中的圖檔、語音,如果下載下傳失敗,最多嘗試5次。關于you-get
參考這篇文章《 You-Get:支援 80 多個網站的指令行多媒體下載下傳器 》了解其更多。you-get
第64-69行:
- 等待所有程序執行結束。
-
exec 1000>&-
是關閉exec 1000<&-
fd1000
該文首發
《虛懷若谷》個人部落格,轉載前請務必署名,轉載請标明出處。
古之善為道者,微妙玄通,深不可識。夫唯不可識,故強為之容:
豫兮若冬涉川,猶兮若畏四鄰,俨兮其若客,渙兮若冰之釋,敦兮其若樸,曠兮其若谷,混兮其若濁。
孰能濁以靜之徐清?孰能安以動之徐生?
保此道不欲盈。夫唯不盈,故能敝而新成。
請關注我的微信公衆号:下雨就像彈鋼琴,Thanks(・ω・)ノ