理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

背景

日常運維工作中編寫shell腳本處理事務,很多時候需要一次性處理很多,需要用到循環,但是循環體內還是線性的,還是要一個個處理,這樣並不會節省很多時間,只是節省了人工一次次輸入的繁瑣。但是對於提高處理能力,沒有實質性的提高。這就需要考慮併發。但Shell中並沒有真正意義的多線程,要實現多線程可以啟動多個後端進程,最大程度利用cpu性能。即:多進程併發,本篇教程由淺入深詳細介紹了shell中的多進程併發。


理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

範例

<code>#!/bin/bashtrap "exec 1000>&-;exec 1000testfiform -fr testfifofor((n=1;n<=10;n++))do    echo >&1000donestart=`date "+%s"`for((i=1;i<=100;i++))do    read -u1000    {        echo "success $i";        sleep 5        echo >&1000    }&donewaitend=`date "+%s"`echo "Time: `expr $end - $start`"exec 1000>&-exec 1000/<code>

實驗

所謂的多進程只不過是將多個任務放到後臺執行而已,很多人都用到過,所以現在講的主要是控制,而不是實現。

實驗一

先看一個小shell:


理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

看執行結果:


理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

很明顯是8s,這種不佔處理器卻有很耗時的進程,我們可以通過一種後臺運行的方式
來達到節約時間的目的。

實驗二

如下為改進:


理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

用“{}”將主執行程序變為一個塊,用&放入後臺,四次執行全部放入後臺後,我們需要用一個wait指令,等待所有後臺進程執行結束,不然 系統是不會等待的,直接繼續執行後續指令,知道整個程序結束。
看結果:

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

可以看到,時間已經大大縮短了!


實驗三

以上實驗雖然達到了多線程併發的目的,但有一個缺陷,不能控制運行在後臺的進程數。為了控制進程,我們引入了管道 和文件操作符。

無名管道: 就是我們經常使用的 例如: cat text | grep “abc” 那個“|”就是管道,只不過是無名的,可以直接作為兩個進程的數據通道
有名管道: mkfilo 可以創建一個管道文件 ,例如: mkfifo fifo_file

管道有一個特點,如果管道中沒有數據,那麼取管道數據的操作就會停滯,直到管道內進入數據,然後讀出後才會終止這一操作,同理,寫入管道的操作,如果沒有讀取操作,這一個動作也會停滯。

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

當我們試圖用echo想管道文件中寫入數據時,由於沒有任何進程在對它做讀取操作,所以它會一直停留在那裡等待讀取操作,此時我們在另一終端上用cat指令做讀取操作

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

你會發現讀取操作一旦執行,寫入操作就可以順利完成了,同理,先做讀取操作也是一樣的:

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

由於沒有管道內沒有數據,所以讀取操作一直滯留在那裡等待寫入的數據

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

一旦有了寫入的數據,讀取操作立刻順利完成

以上實驗,看以看到,僅僅一個管道文件似乎很難實現 我們的目的(控制後臺線程數), 所以 接下來介紹 文件操作符,這裡只做簡單的介紹,如果不熟悉的可以自行查閱資料。
系統運行起始,就相應設備自動綁定到了 三個文件操作符 分別為 0 1 2 對應 stdin ,stdout, stderr 。
在 /proc/self/fd 中 可以看到 這三個三個對應文件

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

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

我們可以exec 指令自行定義、綁定文件操作符,文件操作符一般從3–(n-1)都可以隨便使用
此處的n 為 ulimit -n 的定義值得

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

可以看到 我的 n值為1024 ,所以文件操作符只能使用 0-1023,可自行定義的 就只能是 3-1023 了。

直接上代碼,然後根據代碼分析每行代碼的含義:

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

代碼解釋

第3行: 接受信號 2 (ctrl +C)做的操作。exec 1000>&-和exec 1000testfifo 來實現,但關閉時必須分開來寫,> 讀的綁定,< 標識寫的綁定 <> 則 標識 對文件描述符 1000的所有操作等同於對管道文件testfifo的操作。

第5-7行:分別為 創建管道文件,文件操作符綁定,刪除管道文件
     可能會有疑問,為什麼不能直接使用管道文件呢? 
     事實上,這並非多此一舉,剛才已經說明了管道文件的一個重要特性了,那就是讀寫必須同時存在
     缺少某一種操作,另一種操作就是滯留,而綁定文件操作符 正好解決了這個問題。

第9-12 行: 對文件操作符進行寫入操作。通過一個for循環寫入10個空行,這個10就是我們要定義的後臺線程數 量。
為什麼寫入空行而不是10個字符呢 ?
這是因為,管道文件的讀取 是以行為單位的。

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

當我們試圖用 read 讀取管道中的一個字符時,結果是不成功的,而剛才我們已經證實使用cat是可以 讀取的。

第17-24行:這裡假定我們有100個任務,我們要實現的時 ,保證後臺只有10個進程在同步運行 。read -u1000 的 作用是:讀取一次管道中的一行,在這兒就是讀取一個空行。減少操作附中的一個空行之後,執行一 次任務(當然是放到後臺執行),需要注意的是,這個任務在後臺執行結束以後會向文件操作符中寫 入一個空行,這就是重點所在,如果我們不在某種情況某種時刻向操作符中寫入空行,那麼結果就 是:在後臺放入10個任務之後,由於操作符中沒有可讀取的空行,導致 read -u1000 這兒 始終停頓。

後邊的 就不用解釋了。

貼下執行結果:

理解Shell腳本中的多進程和多線程併發,讓工作效率提升1000倍

每次的停頓中都能看到 只有10個進程在運行
一共耗時50s 一共100個任務,每次10個 ,每個5s 正好50s。上邊的結果圖之所以這麼有規律,這是因為我們所執行的100個任務耗時都是相同的。

比如,系統將第一批10個任務放入後臺的過程所消耗的時間 幾乎可以忽略不計,也就是說這10個任務幾乎可以任務是同時運行,當然也就可以認為是同時結束了,而按照剛才的分析,一個任務結束時就會向文件描述符寫入空行,既然是同時結束的,那麼肯定是同時寫入的空行,所以下一批任務又幾乎同時運行,如此循環下去的。實際應用時,肯定不是這個樣子的,比如,第一個放到後臺執行的任務,是最耗時間的,那他肯定就會是最後一個執行完畢。所以,實際上來說,只要有一個任務完成,那麼下一個任務就可以被放到後臺併發執行了。


分享到:


相關文章: