首頁 > 軟體

Linux Shell多執行緒實現

2020-06-16 17:11:51

情景

shell指令碼的執行效率雖高,但當任務量巨大時仍然需要較長的時間,尤其是需要執行一大批的命令時。因為預設情況下,shell指令碼中的命令是序列執行的。如果這些命令相互之間是獨立的,則可以使用“並行”的方式執行這些命令,這樣可以更好地利用系統資源,提升執行效率,縮短指令碼執行的時間。如果命令相互之間存在互動,則情況就複雜了,那麼不建議使用shell指令碼來完成多執行緒的實現。

為了方便闡述,使用一段測試程式碼。在這段程式碼中,通過seq命令輸出1到10,使用for...in語句產生一個執行10次的迴圈。每一次迴圈都執行sleep 1,並echo出當前迴圈對應的數位。

注意:

  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"

執行結果:

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中並沒有真正的多執行緒,但上述解決方案可以實現“多執行緒”的效果,重要的是,在實際編寫指令碼時應有這樣的考慮和實現。
另外:
方案3、4、5雖然都可以控制並行數量,但方案3顯然寫起來太繁瑣。
方案4和5都以非常簡潔的形式完成了控制並行數的效果,但由於方案5的parallel命令非常強大,所以十分建議系統學習下。
方案3、4、5設定的並行數均為5,實際編寫時可以將該值作為一個引數傳入。

相關知識點

  • wait命令
  • &後台執行
  • 檔案描述符、mkfifo等
  • xargs命令
  • parallel命令

本文永久更新連結地址http://www.linuxidc.com/Linux/2017-06/145162.htm


IT145.com E-mail:sddin#qq.com