shell腳本的執行效率雖高,但當任務量巨大時仍然須要較長的時間,尤爲是須要執行一大批的命令時。由於默認狀況下,shell腳本中的命令是串行執行的。若是這些命令相互之間是獨立的,則可使用「併發」的方式執行這些命令,這樣能夠更好地利用系統資源,提高運行效率,縮短腳本執行的時間。若是命令相互之間存在交互,則狀況就複雜了,那麼不建議使用shell腳原本完成多線程的實現。linux
爲了方便闡述,使用一段測試代碼。在這段代碼中,經過seq
命令輸出1到10,使用for...in
語句產生一個執行10次的循環。每一次循環都執行sleep 1
,並echo
出當前循環對應的數字。shell
注意:bash
請根據真實場景的各類狀況理解本文想要表達的內容。多線程
$ cat test1.sh
#/bin/bash all_num=10 a=$(date +%H%M%S) for num in `seq 1 ${all_num}` do sleep 1 echo ${num} done b=$(date +%H%M%S) echo -e "startTime:\t$a" echo -e "endTime:\t$b"
經過上述代碼可知,爲了體現執行的時間,將循環體開始先後的時間打印了出來。併發
運行結果:學習
$ sh test1.sh
1 2 3 4 5 6 7 8 9 10 startTime: 193649 endTime: 193659
10次循環,每次sleep 1秒,因此總執行時間10s。測試
在linux中,在命令的末尾加上&
符號,則表示該命令將在後臺執行,這樣後面的命令不用等待前面的命令執行完就能夠開始執行了。示例中的循環體內有多條命令,則能夠以{}
括起來,在大括號後面添加&
符號。操作系統
$ cat test2.sh
#/bin/bash all_num=10 a=$(date +%H%M%S) for num in `seq 1 ${all_num}` do { sleep 1 echo ${num} } & done b=$(date +%H%M%S) echo -e "startTime:\t$a" echo -e "endTime:\t$b"
運行結果:.net
sh test2.sh
startTime: 194147 endTime: 194147 [j-tester@merger142 ~/bin/multiple_process]$ 1 2 3 4 5 6 7 8 9 10
經過結果可知,程序沒有先打印數字,而是直接輸出了開始和結束時間,而後顯示出了命令提示符[j-tester@merger142 ~/bin/multiple_process]$
(出現命令提示符表示腳本已運行完畢),而後纔是數字的輸出。這是由於循環體內的命令所有進入後臺,因此均在sleep了1秒之後輸出了數字。開始和結束時間相同,即循環體的執行時間不到1秒鐘,這是因爲循環體在後臺執行,沒有佔用腳本主進程的時間。線程
wait
命令解決上面的問題,只須要在上述循環體的done語句後面加上wait
命令,該命令等待當前腳本進程下的子進程結束,再運行後面的語句。
$ cat test3.sh
#/bin/bash all_num=10 a=$(date +%H%M%S) for num in `seq 1 ${all_num}` do { sleep 1 echo ${num} } & done wait b=$(date +%H%M%S) echo -e "startTime:\t$a" echo -e "endTime:\t$b"
運行結果:
$ sh test3.sh
1 2 3 4 5 6 7 9 8 10 startTime: 194221 endTime: 194222
但這樣依然存在一個問題:
由於&
使得全部循環體內的命令所有進入後臺運行,那麼假若循環的次數不少,會使操做系統在瞬間建立出全部的子進程,這會很是消耗系統的資源。若是循環體內的命令又很消耗系統資源,則結果可想而知。
最好的方法是併發的進程是可配置的。
$ cat test4.sh
#/bin/bash all_num=10 # 設置併發的進程數 thread_num=5 a=$(date +%H%M%S) # mkfifo tempfifo="my_temp_fifo" mkfifo ${tempfifo} # 使文件描述符爲非阻塞式 exec 6<>${tempfifo} rm -f ${tempfifo} # 爲文件描述符建立佔位信息 for ((i=1;i<=${thread_num};i++)) do { echo } done >&6 # for num in `seq 1 ${all_num}` do { read -u6 { sleep 1 echo ${num} echo "" >&6 } & } done wait # 關閉fd6管道 exec 6>&- b=$(date +%H%M%S) echo -e "startTime:\t$a" echo -e "endTime:\t$b"
運行結果:
$ sh test4.sh
1 3 2 4 5 6 7 8 9 10 startTime: 195227 endTime: 195229
xargs -P
控制併發數xargs命令有一個-P
參數,表示支持的最大進程數,默認爲1。爲0時表示儘量地大,即方案2
的效果。
$ cat test5.sh
#/bin/bash all_num=10 thread_num=5 a=$(date +%H%M%S) seq 1 ${all_num} | xargs -n 1 -I {} -P ${thread_num} sh -c "sleep 1;echo {}" b=$(date +%H%M%S) echo -e "startTime:\t$a" echo -e "endTime:\t$b"
運行結果:
$ sh test5.sh
1 2 3 4 5 6 8 7 9 10 startTime: 195257 endTime: 195259
GNU parallel
命令控制併發數GNU parallel
命令是很是強大的並行計算命令,使用-j
參數控制其併發數量。
$ cat test6.sh
#/bin/bash all_num=10 thread_num=6 a=$(date +%H%M%S) parallel -j 5 "sleep 1;echo {}" ::: `seq 1 10` b=$(date +%H%M%S) echo -e "startTime:\t$a" echo -e "endTime:\t$b"
運行結果:
$ sh test6.sh
1 2 3 4 5 6 7 8 9 10 startTime: 195616 endTime: 195618
「多線程」的好處不言而喻,雖然shell中並無真正的多線程,但上述解決方案能夠實現「多線程」的效果,重要的是,在實際編寫腳本時應有這樣的考慮和實現。
另外:
方案三、四、5雖然均可以控制併發數量,但方案3顯然寫起來太繁瑣。
方案4和5都以很是簡潔的形式完成了控制併發數的效果,但因爲方案5的parallel命令很是強大,因此十分建議系統學習下。
方案三、四、5設置的併發數均爲5,實際編寫時能夠將該值做爲一個參數傳入。
&
後臺運行