在shell腳本里批量執行程序是比較常見的方式,如果程序很多,每個執行時間比較長,則順序執行需要花費大量的時間。
此時並發就成為我們考慮的方向。
上篇《shell多線程》中我們已經簡單實現了基於for循環的並發,可以顯著提高工作效率;
缺點是CPU的核心不是無限的,如果全部占用,則會影響系統的正常運行。
這個時候我們就考慮利用linux系統的管道來進行最大並發數的管控。
1.舉例:
一個廁所有10個蹲位,如果100個人來使用,則勢必形成競爭,這時管理員給每個蹲位一個鎖和一把鑰匙,先來的人拿鑰匙開鎖開始使用;
蹲位全部占滿后后面的人等待,當有一個蹲位空出,則交出鑰匙給等待隊列中的第一個人,如此循環,直到等待隊列為空。
2.文件描述符
管道具有存一個讀一個,讀完一個就少一個,沒有則阻塞,放回的可以重復取,這正是隊列特性,解決這個問題的關鍵就是文件描述符了。
3. mkfifo /tmp/fd1
創建有名管道文件exec 3<>/tmp/fd1,創建文件描述符3關聯管道文件,這時候3這個文件描述符就擁有了管道的所有特性,還具有一個管道不具有的特性:無限存不阻塞,無限取不阻塞,而不用關心管道內是否為空,也不用關心是否有內容寫入引用文件描述符: &3可以執行n次echo >&3 往管道里放入n把鑰匙
4.完整代碼
#!/bin/bash [ -e /tmp/fd1 ] || mkfifo /tmp/fd1 #創建有名管道 exec 3<>/tmp/fd1 #創建文件描述符,以可讀(<)可寫(>)的方式關聯管道文件,這時候文件描述符3就有了有名管道文件的所有特性 rm -rf /tmp/fd1 #關聯后的文件描述符擁有管道文件的所有特性,所以這時管道文件可以刪除,我們留下文件描述符來用就可以 for ((i=1;i<=10;i++)) do echo >&3 #&3代表引用文件描述符3,這條命令代表往管道里面放入了一個"令牌",文件描述符可以使用0/1/2/225之外的其他數字,這幾個已被占用 done for ((i=1;i<=100;i++)) do read -u3 #代表從管道中讀取一個令牌 { sleep 1 #sleep 1用來模仿執行一條命令需要花費的時間(可以用真實命令來代替) echo 'success'$i echo >&3 #代表我這一次命令執行到最后,把令牌放回管道 }& done wait exec 3<&- #關閉文件描述符的讀 exec 3>&- #關閉文件描述符的寫
4.由於從前寫的腳本大部分都是以方法的形式存在的,所以想要落地就需要對上面的腳本做一些修改,保證每次循環都會執行一個方法
t1Fun(){ echo 1 } t2Fun(){ echo 2 } t3Fun(){ echo 3 } t4Fun(){ echo 4 } [ -e /tmp/fd1 ] || mkfifo /tmp/fd1 exec 3<>/tmp/fd1 rm -rf /tmp/fd1 for ((i=1;i<5;i++)) do echo >&3 done for ((i=1;i<=4;i++)) do read -u3 { sleep 1 if [ $i -eq 1 ];then t1Fun elif [ $i -eq 2 ];then t2Fun elif [ $i -eq 3 ];then t3Fun else t4Fun fi echo >&3 }& done wait exec 3<&- exec 3>&-