主要記錄一下Shell腳本中的命令的併發和串行執行。
默認的情況下,Shell腳本中的命令是串行執行的,必須等到前一條命令執行完後才執行接下來的命令,但是如果我有一大批的的命令需要執行,而且互相又沒有影響的情況下(有影響的話就比較複雜了),那麼就要使用命令的併發執行了。
看下面的代碼:
#!/bin/bash for(( i = 0; i < ${count}; i++ )) do commands1 done commands2
修改後的代碼如下:
#!/bin/bash for(( i = 0; i < ${count}; i++ )) do { commands1 }& done commands2
這樣的話commands1就可以並行執行了。 實質是將commands1作爲後臺進程在執行,這樣主進程就不用等待前面的命令執行完畢之後纔開始執行接下來的命令。
但是我的本來目的是讓commands1的這個循環都執行結束後,再用command2去處理前面的結果。如果像上面這樣寫的話,在commands1都還沒結束時就已經開始執行commands2了,得到了錯誤的結果。
再次修改代碼如下:copy
#!/bin/bash for(( i = 0; i < ${count}; i++ )) do { commands1 }& done wait commands2
上面這樣就可以達到預期的目的了,先是所有的commands1在後臺並行執行,等到循環裏面的命令都結束之後才執行接下來的commands2。
對於上面的代碼,如果count值特別大的時候,我們應該控制併發進程的個數,不然會影響系統其他進程的運行,甚至死機。
下面的代碼控制併發個數。其實是利用令牌原理實現,一個線程要運行,首先要拿到令牌在該代碼中即read一行數據,讀取不到數據就會暫停,否則就拿到數據就運行命令任務,完成後將令牌再放回,也即再在管道文件中寫入一行數據,這裏的數據是換行符,echo >&4。這樣另外的線程就可以再讀該數據(拿到令牌),並運行。
#!/bin/bash tmpfile=$$.fifo #創建管道名稱 mkfifo $tmpfile #創建管道 exec 4<>$tmpfile #創建文件標示4,以讀寫方式操作管道$tmpfile rm $tmpfile #將創建的管道文件清除 thred=4 #指定併發個數 seq=(1 2 3 4 5 6 7 8 9 21 22 23 24 25 31 32 33 34 35) #創建任務列表 # 爲併發線程創建相應個數的佔位 { for (( i = 1;i<=${thred};i++ )) do echo; #因爲read命令一次讀取一行,一個echo默認輸出一個換行符,所以爲每個線程輸出一個佔位換行 done } >&4 #將佔位信息寫入管道 for id in ${seq[*]} #從任務列表 seq 中按次序獲取每一個任務 或者用: for id in ${seq} do read #讀取一行,即fd4中的一個佔位符 (./ur_command ${id};echo >&4 ) & #在後臺執行任務ur_command 並將任務 ${id} 賦給當前任務ur_command;任務執行完後在fd4種寫入一個佔位符 ,&表示該部分命令/任務 並行處理 done <&4 #指定fd4爲整個for的標準輸入 wait #等待所有在此shell腳本中啓動的後臺任務完成 exec 4>&- #關閉管道
整個流程中read 和 echo 對fd4的交替寫入和讀取是併發處理的關鍵
可以想象 如果read 命令發現fd4中沒有數據時 將等待fd4的數據
但是這有個缺點就是每個線程運行前都要拿到令牌,而且這個令牌的拿放是要消耗I/O的,有沒有更好的方法實現?
可以用PV語句實現。也就是定義一個全局變量globleThread_num用來存放最大線程個數,還要有一個變量currentThread_num存放當前的線程個數,然後需要鎖,當讀取和改變currentThread_num的值的時候,需要鎖住。