情景linux--shell如何實現多線程?

情景linux--shell如何實現多線程?

情景

shell腳本的執行效率雖高,但當任務量巨大時仍然須要較長的時間,尤爲是須要執行一大批的命令時。由於默認狀況下,shell腳本中的命令是串行執行的。若是這些命令相互之間是獨立的,則可使用「併發」的方式執行這些命令,這樣能夠更好地利用系統資源,提高運行效率,縮短腳本執行的時間。若是命令相互之間存在交互,則狀況就複雜了,那麼不建議使用shell腳原本完成多線程的實現。linux

爲了方便闡述,使用一段測試代碼。在這段代碼中,經過seq命令輸出1到10,使用for...in語句產生一個執行10次的循環。每一次循環都執行sleep 1,並echo出當前循環對應的數字。shell

注意:bash

  1. 真實的使用場景下,循環次數不必定等於10,或高或低,具體取決於實際的需求。
  2. 真實的使用場景下,循環體內執行的語句每每比較耗費系統資源,或比較耗時等。

請根據真實場景的各類狀況理解本文想要表達的內容多線程

$ 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。測試

方案

方案1:使用"&"使命令後臺運行

在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秒鐘,這是因爲循環體在後臺執行,沒有佔用腳本主進程的時間。線程

方案2:命令後臺運行+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

但這樣依然存在一個問題:
由於&使得全部循環體內的命令所有進入後臺運行,那麼假若循環的次數不少,會使操做系統在瞬間建立出全部的子進程,這會很是消耗系統的資源。若是循環體內的命令又很消耗系統資源,則結果可想而知。

最好的方法是併發的進程是可配置的。

方案3:使用文件描述符控制併發數

$ 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

方案4:使用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

方案5:使用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,實際編寫時能夠將該值做爲一個參數傳入。

參考文章

  1. http://blog.csdn.net/qq_34409701/article/details/52488964
  2. https://www.codeword.xyz/2015/09/02/three-ways-to-script-processes-in-parallel/
  3. http://www.gnu.org/software/parallel/

相關知識點

  • wait命令
  • &後臺運行
  • 文件描述符、mkfifo等
  • xargs命令
  • parallel命令
相關文章
相關標籤/搜索