shell中實現多進程實際上就是將多個任務放到後臺中執行而已,可是如今須要控制多進程併發的數量該如何實現呢?別急,咱們一步一步來實現這個目標,首先從最原始的串行執行開始:node
#!/bin/bash start=`date +%s` for i in $(seq 1 5); do echo test sleep 2 done end=`date +%s` time=$(($end - $start)) echo "time: $time"
執行結果:shell
# sh test1.sh test test test test test time: 10
這是最原始的一種處理方式,串行執行,沒法有效利用計算機的資源,而且效率低下。若是能夠一次執行多個任務的,把它放到後臺便可,咱們作以下改進:bash
#!/bin/bash start=`date +%s` for i in $(seq 1 5); do { echo test sleep 2 }& done wait end=`date +%s` time=$(($end - $start)) echo "time: $time"
首先看下執行結果:併發
# sh test2.sh test test test test test time: 2
說下改動,首先我把for循環中的代碼用{}包爲一個塊,而後增長&符號使其後臺運行,以後增長wait指令,該指令會等待全部後臺進程結束,若是不加wait,程序直接往下走,最終打出的time將會是0。如今程序已經由以前的10秒縮短爲2秒,彷佛效果不錯,不過試想這樣一個場景,有1000個這樣的任務,若是仍是以這種方式執行,機器負載是扛不住的,咱們必須想一種辦法來控制進程的併發數,那就是管道和文件描述符。
首先介紹下管道(pipe):ide
管道有一個顯著的特色,若是管道里沒有數據,那麼去取管道數據時,程序會阻塞住,直到管道內進入數據,而後讀取才會終止這個操做,反之,管道在執行寫入操做時,若是沒有讀取操做,一樣會阻塞,下面是實例:函數
# mkfifo pipefile # echo test > pipefile # 此時會阻塞在這
此時我新開一個窗口再執行讀操做code
# cat pipefile test
這時窗口一中的進程纔會終止,再來看先讀的狀況:進程
# cat pipefile # 一樣阻塞停滯在此
新開窗口執行寫操做:ip
# echo test > pipefile
這時窗口一會馬上讀出內容並順利完成
接下來看一下文件描述符
Linux系統在初始運行時,會自動綁定三個文件描述符0 1 2 對應 stdin ,stdout, stderr,在/proc/self/fd能夠找到資源
# cd /proc/self/fd # ll total 0 lrwx------. 1 root root 64 Mar 27 03:22 0 -> /dev/pts/0 lrwx------. 1 root root 64 Mar 27 03:22 1 -> /dev/pts/0 lrwx------. 1 root root 64 Mar 27 03:22 2 -> /dev/pts/0 lrwx------. 1 root root 64 Mar 27 03:23 255 -> /dev/pts/0 # 綁定到咱們的終端設備上 # echo test > /proc/self/fd/1 test # echo test > /proc/self/fd/2 test
輸出到幾個文件的內容會打印到控制檯上
咱們可使用exec 指令自行定義、綁定文件描述符,文件描述符的取值範圍是3-(ulimit -n)-1
在我本機這個範圍是3-1023
# ulimit -n 1024 # exec 1024<>pipefile -bash: 1024: Bad file descriptor # exec 1000<>pipefile #
下面開始介紹如何使用管道文件和文件描述符來控制進程併發:
mkfifo tm1 exec 5<>tm1 rm -f tm1
首先建立一個管道文件tm1,而後使用exec命令將該文件的輸入輸出綁定到5號文件描述符,然後刪除該管道文件,這裏刪除的只是該文件的Inode,實際文件已由內核open函數打開,這裏刪除並不會影響程序執行,當程序執行完後,文件描述符會被系統自動回收。
for ((i=1;i<=10;i++)); do echo >&5 done
經過一個for循環向該文件描述符寫入10個空行,這個10其實就是咱們定義的後臺進程數量,這裏須要注意的是,管道文件的讀取是以行爲單位的。
for ((j=1;j<=100;j++)); do read -u5 { echo test$j sleep 2 echo >&5 }& done wait
這裏假定我後臺有100個任務,而咱們在後臺保證只有10個進程在同時運行
read -u5 的做用是,讀取5號文件描述符中的一行,就是讀取一個空行
在減小文件描述符的一個空行以後,在後臺執行一次任務,而任務在執行完成之後,會向文件描述符中再寫入一個空行,這是爲何呢,由於若是咱們寫入空行的話,當後臺放入了10個任務以後,因爲沒有可讀取的空行,read -u5就會被阻塞住!
exec 5>&- exec 5<&-
咱們生成作綁定時 能夠用 exec 5<>tm1 來實現,但關閉時必須分開來寫> 讀的綁定,< 標識寫的綁定 <> 則標識 對文件描述符5的全部操做等同於對管道文件tm1的操做。
完整代碼以下:
#!/bin/bash mkfifo tm1 exec 5<>tm1 rm -f tm1 for ((i=1;i<=10;i++)); do echo >&5 done for ((j=1;j<=100;j++)); do read -u5 { echo test$j sleep 2 echo >&5 }& done wait exec 5>&- exec 5<&-
這樣,就實現了進程的併發控制