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操作該管道。
管道符寫入讀取

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

通過以上實驗,看以看到,僅僅一個管道文件似乎很難實現控制后台線程數,因此我們接下來簡單介紹 文件操作符

文件操作符

系統運行起始,就相應設備自動綁定到了 三個文件操作符 分別為012 對應 stdinstdoutstderr 。在 /proc/self/fd 或者/dev/fd中可以看到這三個對應文件:

linux fd

輸出到這三個文件的內容都會顯示出來。只是因為顯示器作為最常用的輸出設備而被綁定。

在Linux中,可以通過exec指令自行定義、綁定文件操作符,文件操作符一般從3~(n-1)都可以隨便使用,此處的nulimit -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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM