實踐雜談(1)—— Bash腳本實現並行化和進程數控制

在項目過程中,我們常常需要對大量文件進行批量處理。然而,如果每個文件都需要一定的處理時間,而文件數量又很大,逐個的處理會耗費大量的時間,大大影響工作的效率。這時,如果各個文件的處理是相互獨立的話,我們自然希望多個文件能夠並行化地進行處理,最大限度地利用計算機資源來提高工作效率。

本文介紹兩種最爲常見的利用Bash腳本實現並行化的方法。值得一提的是,由於硬件條件的限制,我們通常會對並行化的進程數目進行控制。(博主便曾因爲佔用太多公共資源而遭到投訴了。。)

——————————————————————————————————————————————————————

  1. 後臺 —— 最樸素的並行化
  2. FIFO —— 常見的進程控制方法
  3. xargs —— 逆天的方法

1. 後臺操作

在linux中,由於後臺運行的進程不會影響前臺的操作,我們可以通過將正在運行的進程放到後臺運行來實現進程的並行操作。將進程放到後臺的方法有兩種:
1. &
通過在執行命令時在命令行最後加上“&”符號,便可簡單實現進程的後臺操作。
2. Ctrl Z + bg
Ctrl Z 實現的是講運行的進程掛起,bg實現將進程放到後臺繼續運行。

兩種方法都會返回一個該進程的後臺編號,表示該進程正在後臺運行。我們可以通過 jobs 命令查看當前後臺進程,也可以通過 fg %<id> 將進程放回前臺。



2. FIFO 命名管道

爲了方便討論,我們拿以下簡單程序段作爲文件處理的核心代碼:



  • 什麼是FIFO?
FIFO(命名管道)是一種進程之間進行通信的機制,它是一種特殊的文件類型,可以像平常的文件一樣進行類似的讀寫操作,但又像匿名管道一樣具有進程通信的功能。命名管道相對於匿名管道的優勢在於,不要求進程之間具有親緣關係,能夠實現無關進程之間的通信。

  • 如何用FIFO實現並行化?

爲了實現多個進程並行化,我們創建一個FIFO文件作爲進程池,並通過設置一定數目的“令牌”實現併發進程的數目。每個進程依次進入進程池領取“令牌”,若領取成功(有空餘令牌)則運行,完成後歸還令牌;若領取失敗,則等待其它進程釋放令牌。

閒話少說,我們直接看代碼吧:

<pre name="code" class="plain">Nproc=3	# the limit number of processes
Pfifo="/tmp/$$.fifo"    # create a fifo type file
mkfifo $Pfifo     # create a named pipe
exec 6<>$Pfifo     # fd is 6
rm -f $Pfifo

# Initialize the pipe
for((i=1; i<=$Nproc; i=i+1)); do
    echo
done >&6

filelist="text.txt"
# Loop
while read line
do
    read -u6
    {
        ./test.sh $fn
        echo >&6
    } &
done < $filelist

wait     # waiting for all the background processes finished
exec 6>&-

我們來逐行分析一下:

1. Nproc=3 設定同時執行的進程數上限

2. 新建一個後綴爲fifo的FIFO文件,以PID作爲文件名以避免出現重名情況

3. mkfifo命令,以上面的文件名創建一個命名管道

4. 以讀寫方式打開命名管道,並設置文件標識符爲6

5. 刪除FIFO文件,可有可無

8~10. 往命名管道中寫入Nproc個空行,用來模擬Nproc個令牌

16. 從命名管道中讀取一行,模擬領取一個令牌。由於FIFO特殊的讀寫機制,若沒有空餘的行可以讀取,則進程會等待直至有可以讀取的空餘行

17~20. 若領取到令牌,運行核心程序段,完成後往命名管道寫入一行,模擬歸還令牌操作;這些操作都是在後臺完成,故之後加上 & 命令

23. 等待所有後臺進程完成

24. 釋放文件標識符

3. xargs

上面囉嗦了這麼多,最後我們介紹一種極其簡單的方法,一行代碼解決並行化操作以及控制進程數目兩個任務!

xargs可以理解爲一個用於傳遞參數列表的命令,結合 cat 和 pipe 命令,我們可以高效地將文件中每行內容作爲程序的輸入參數並實現並行化。繼續沿用上面的例子,我們只需要寫這麼一行代碼:

cat text.txt | xargs -L 1 -P 3 -I {} ./test.sh {}

難以置信?我們來寫個小腳本測試一下它的效果吧:



實驗結果:



簡直是又簡單又強大有木有!!
下面來介紹一下里面的奧妙吧。
-L 參數控制每次讀取的最大行數,當然是一行一行讀咯。
-P 參數控制最大使用的進程數目,這裏就是實現並行化和進程數控制的關鍵所在啦!(未免也太方便。。)
-I 參數控制需要替換的參數位置。

That's all! 意猶未盡?那我們再來看幾個小例子,體會一下xargs命令的強大之處吧!

  • 刪除文件名包含空格的文件
find . -names "*.txt" -print0 | xargs -0 rm  -rf

  • 結合 grep 命令
find . -name '*.c' | xargs grep 'stdlib.h'


Reference:

  1. Bash腳本實現批量作業並行化:http://blog.sciencenet.cn/blog-548663-750136.html

  2. Linux下進程通信:命名管道: http://cpp.ezbty.org/content/science_doc/linux下進程間通信:命名管道_mkfifo

  3. 進程間通信——使用匿名管道: http://blog.csdn.net/ljianhui/article/details/10168031

  4. 進程間通信——使用命名管道:http://blog.csdn.net/ljianhui/article/details/10202699

特別鳴謝:Yuncheng Li 學長的耐心指導!


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