在shell腳本中,咱們想要實現多進程高併發,最簡單的方法是把命令丟到後臺去,若是量不大的話,沒問題。 可是若是有幾百個進程同一時間丟到後臺去就很恐怖了,對於服務器資源的消耗很是大,甚至致使宕機。
web
那有沒有好的解決方案呢? 固然有!shell
咱們先來學習下面的常識。
數據庫
1 文件描述符bash
文件描述符(縮寫fd)在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。每個unix進程,都會擁有三個標準的文件描述符,來對應三種不一樣的流:服務器
除了上面三個標準的描述符外,咱們還能夠在進程中去自定義其餘的數字做爲文件描述符,後面例子中會出現自定義數字。每個文件描述符會對應一個打開文件,同時,不一樣的文件描述符也能夠對應同一個打開文件;同一個文件能夠被不一樣的進程打開,也能夠被同一個進程屢次打開。併發
咱們能夠寫一個測試腳本/tmp/test.sh,內容以下:
less
#!/bin/bash socket
echo "該進程的pid爲$$"ide
exec 1>/tmp/test.log 2>&1函數
ls -l /proc/$$/fd/
執行該腳本 sh /tmp/test.sh,而後查看/tmp/test.log內容爲:
總用量 0
lrwx------ 1 root root 64 11月 22 10:26 0 -> /dev/pts/3
l-wx------ 1 root root 64 11月 22 10:26 1 -> /tmp/test.log
l-wx------ 1 root root 64 11月 22 10:26 2 -> /tmp/test.log
lr-x------ 1 root root 64 11月 22 10:26 255 -> /tmp/test.sh
lrwx------ 1 root root 64 11月 22 10:26 3 -> socket:[196912101]
其中0爲標準輸入,也就是當前終端pts/3,1和2所有指向到了/tmp/test.log,另外兩個數字,我們暫時不關注。
2 命名管道
咱們以前接觸過的管道「1」,其實叫作匿名管道,它左邊的輸出做爲右邊命令的輸入。這個匿名管道只能爲兩邊的命令提供服務,它是沒法讓其餘進程鏈接的。
實際上,這兩個進程(cat和less)並不知道管道的存在,它們只是從標準文件描述符中讀取數據和寫入數據。
另一種管道叫作命名管道,英文(First In First Out,簡稱FIFO)。
FIFO本質上和匿名管道的功能同樣,只不過它有一些特色:
1)在文件系統中,FIFO擁有名稱,而且是以設備特俗文件的形式存在的;
2)任何進程均可以經過FIFO共享數據;
3)除非FIFO兩端同時有讀與寫的進程,不然FIFO的數據流通將會阻塞;
4)匿名管道是由shell自動建立的,存在於內核中;而FIFO則是由程序建立的(好比mkfifo命令),存在於文件系統中;
5)匿名管道是單向的字節流,而FIFO則是雙向的字節流;
有了上面的基礎知識儲備後,下面咱們來用FIFO來實現shell的多進程併發控制。
需求背景:
領導要求小明備份數據庫服務器裏面的100個庫(數據量在幾十到幾百G),須要以最快的時間完成(5小時內),而且不能影響服務器性能。
需求分析:
因爲數據量比較大,單個庫備份時間少則10幾分鐘,多則幾個小時,咱們算平均每一個庫30分鐘,若一個庫一個庫的去備份,則須要3000分鐘,至關於50個小時。很明顯不可取。但所有丟到後臺去備份,100個併發,數據庫服務器也沒法承受。因此,須要寫一個腳本,可以控制併發數就能夠實現了。
控制併發的shell腳本示例:
#!/bin/sh
function a_sub {
sleep 2;
endtime=`date +%s`
sumtime=$[$endtime-$starttime]
echo "我是$i,運行了2秒,整個腳本已經執行了$sumtime秒"
}
starttime=`date +%s`
export starttime
##其中$$爲該進程的pid
tmp_fifofile="/tmp/$$.fifo"
##建立命名管道
mkfifo $tmp_fifofile
##把文件描述符6和FIFO進行綁定
exec 6<>$tmp_fifofile
##綁定後,該文件就能夠刪除了
rm -f $tmp_fifofile
##併發量爲3,用這個數字來控制併發數
thread=3
for ((i=0;i<$thread;i++));
do
##寫一個空行到管道里,由於管道文件的讀取以行爲單位
echo >&6
done
##循環10次,至關於要備份100個庫
for ((i=0;i<10;i++))
do
##讀取管道中的一行,每次讀取後,管道都會少一行
read -u6
{
a_sub || {echo "a_sub is failed"}
##每次執行完a_sub函數後,再增長一個空行,這樣下面的進程才能夠繼續執行
echo >&6
} & ##這裏要放入後臺去,不然併發實現不了
done
##這裏的wait意思是,須要等待以上全部操做(包括後臺的進程)都結束後,再往下執行。
wait
##關閉文件描述符6的寫
exec 6>&-