[Shell] shell並發


1. for循環並發執行 - 前台命令變后台進程

shell中,后一個前台命令必須等待前一個前台命令執行完畢才能進行,這就是所謂的單線程程序。
shell並沒有真正意義上的多進程。而最簡單的節省時間,達到“ 多線程”效果的辦法,是將前台命令變成后台進程,這樣一來就可以跳過前台命令的限制了。

1.1 用法

for((i=0;i<$num_tasks;i++))
{
}&
done
wait
echo "done"

1.2 問題

以上的實現方法中,同時有num_tasks在后台運行,如果num_tasks個數非常大,那么很可能爆內存。那么如果協調內存和並發效率呢?

1.3 解決方法

控制同時啟動的進程的個數。

2. 控制並發執行的進程個數 - 管道 + 文件操作符實現隊列

2.1 Prerequisities

2.1.1 管道文件

1:無名管道(ps aux | grep nginx)

2:有名管道(mkfifo /tmp/fd1)

有名管道特性:

1. mkfifo /tmp/fd1 創建有名管道

   cat /tmp/fd1 顯示管道中內容,如果管道內容為空,則阻塞

 

2. echo "test" > /tmp/fd1 如果沒有讀管道的操作,則阻塞

在terminal 1 中執行以下命令,向管道中輸入'test',由於沒有讀管道的操作,所以被阻塞了

在terminal 2 中執行以下命令,讀管道中的內容,於是terminal 1 結束了阻塞的狀態。

terminal 2 有了輸出test,而terminal 1中命令終止。

可以利用以上特性維護一個存令牌的隊列,向其中寫入一個令牌,被阻塞,只能這個令牌被讀取之后,才可以結束阻塞的狀態;

如果用於並發進程的控制,以上特性可以用於保證並發的進程數是1。

 

=====  但是,如果想每次並發多個進程要如何處理呢? =====

可以看出管道特性中,阻礙“並發多個”的點在於 “寫入一個即阻塞”,即限制了令牌隊列的長度為1;

那么如果可以不被阻塞,而可以由我們制定令牌長度,這樣並發數就是我們指定的隊列長度,也即隊列中的初始寫入的令牌數了。

如果希望實現“不被阻塞”,那么可以采用“文件描述符”~

2.1.2 文件描述符

1. 簡介

文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。(百度百科)

2. 文件描述符與管道文件關聯

exec [文件描述符fid] <> [管道文件fd]  

該命令含義:創建文件描述符fid(非負整數,可以避開使用0 stdin, 1 stdout, 2 stderr),並關聯(以讀寫方式 <> 打開)管道文件fd。

此時文件描述符fid就擁有了管道的所有特性,還具有一個管道不具有的特性:無限存不阻塞,無限取不阻塞,而不用關心管道內是否為空,也不用關心是否有內容寫入引用文件描述符&fid。

可以執行n次echo >&fid 往管道里放入n個令牌。

2.2 shell 指定並發數的實現

2.2.1 實現邏輯

設置這個隊列的長度為可以並發執行的進程個數$num_para。

(1) 先往這個管道中放置num_para個令牌;

(2) 來了num_para個進程,取走了num_para個令牌;第num_para + 1個進程因為取不到令牌,被阻塞;

(3) 最初取到令牌的進程中,有一個執行完了,釋放令牌到管道中;

(4) 管道中又有令牌了,一個被阻塞的進程可以獲取該令牌,執行;

(5) 循環(3)(4)直至所有進程執行完畢。

2.2.2 實現代碼

start_time=`date +%s`                # 定義腳本運行的開始時間

[ -e /tmp/fd1 ] || mkfifo /tmp/fd1   # 如果有名管道文件不存在,則創建

exec 3<>/tmp/fd1                     # 創建文件描述符3, 並以可讀<, 可寫>的方式關聯管道文件
                                     # 這時文件描述符3就有了有名管道文件的特性

rm -rf /tmp/fd1                      # 關聯后的文件描述符擁有管道文件的所有特性,所以這時候管道文件可以刪除
                                     # 留下文件描述符來用就可以了

for((i=1;i<=10;i++))                 # &3 引用文件描述符3,循環向管道中放入了10個令牌,支持並發數為10
do
    echo >&3
done

for((i=1;i<=100;i++))
do
read -u3                             # 從管道中讀取1個令牌
{
    sleep 1                          # 模擬進程運行
    echo 'success'$i
    echo >&3                         # 該進程運行結束,將其令牌放回管道
}&
done
wait


stop_time=`date +%s`                 # 定義腳本運行的結束時間

echo "Time: `expr $stop_time - $start_time`"   # 獲取執行100條進程總的運行時間

exec 3<&-                            # 關閉文件描述符的讀 
exec 3>&-                            # 關閉文件描述符的寫

輸出:

success1...省略

 

 

參考鏈接:

1. shell隊列實現線程並發控制:https://www.cnblogs.com/chenjiahe/p/6268853.html

2. shell實現多線程筆記:https://blog.51cto.com/mochaming/1279864

3. exec操作文件描述符:http://blog.sina.com.cn/s/blog_7099ca0b0100nby8.html

4. 文件描述符:https://blog.csdn.net/Captain_MXD/article/details/52153233


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM