shell中控制多個進程併發執行的方法

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

  • 無名管道
    它常常出如今咱們的命令中,例如cat /etc/passwd | awk -F: '{print $1}',其中"|"就是管道,它將前一個命令的結果輸出到後一個進程中,做爲兩個進程的數據通道,不過他是無名的。
  • 有名管道
    使用mkfifo命令建立的管道即爲有名管道,例如,mkfifo pipefile, pipefile即爲有名管道。

管道有一個顯著的特色,若是管道里沒有數據,那麼去取管道數據時,程序會阻塞住,直到管道內進入數據,而後讀取才會終止這個操做,反之,管道在執行寫入操做時,若是沒有讀取操做,一樣會阻塞,下面是實例:函數

# 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<&-

這樣,就實現了進程的併發控制

相關文章
相關標籤/搜索