其實這個腳本並沒有實現多線程,shell也根本不可能實現多線程。
此腳本的作用無非是限制幾乎同時放入後臺執行的進程數量而已,從而達到在提高腳本執行效率的同時又不明顯增加負載的作用。
原始腳本如下:
#!/bin/bash
SEND_THREAD_NUM=13
tmp_fifofile="/tmp/$$.fifo"
mkfifo "$tmp_fifofile"
exec 6<>"$tmp_fifofile"
for ((i=0;i<$SEND_THREAD_NUM;i++));do
echo
done >&6
for i in `seq 1 100`;do
read -u6
{
echo $i
sleep 3
echo >&6
} &
pid=$!
echo $pid
done
wait
exec 6>&-
exit 0
SEND_THREAD_NUM=13
tmp_fifofile="/tmp/$$.fifo"
mkfifo "$tmp_fifofile"
exec 6<>"$tmp_fifofile"
for ((i=0;i<$SEND_THREAD_NUM;i++));do
echo
done >&6
for i in `seq 1 100`;do
read -u6
{
echo $i
sleep 3
echo >&6
} &
pid=$!
echo $pid
done
wait
exec 6>&-
exit 0
以下爲詳細註解。
#設置線程數,在這裏所謂的線程,其實就是幾乎同時放入後臺(使用&)執行的進程。
SEND_THREAD_NUM=13
# 腳本運行的當前進程ID號作爲文件名,其實這樣命名只是爲了防止創建管道文件時與現有文件名重複,從而引起創建失敗,別無他用。
tmp_fifofile="/tmp/$$.fifo"
#創建管道文件
mkfifo "$tmp_fifofile"
#把文件描述符6指向管道文件,文件描述符可以使用3-9任意一個即可(除了5),0、1、2、5已有定義,具體可參考關於文件描述符的文章。
EXEC 6<>"$tmp_fifofile"
#刪除管道文件,其實刪不刪無所謂,看你自己了。
rm -f $tmp_fifofile
#使用一個for循環向管道中輸入$SEND_THREAD_NUM個空行;>符號爲輸出重導向;&符號表示6爲文件描述符,也就是說&6表示文件描述符6.
for ((i=0;i<$SEND_THREAD_NUM;i++));do
echo
done >&6
for i in `seq 1 100`;do # 100 次 for 循環開始,也就是說表示100次你的實際應用
read -u6 # 從文件描述符6(即管道)中讀取行,每次讀一行,由於是管道,讀一行,管道中便少一行,每次只能讀取一行,注意:如果讀完了,便會掛起,直到管道中再次有可讀的行。
{
echo $i # 打印i,這裏代表整個腳本的應用部分,可以替換成你的實際應用。
sleep 3 # 暫停3秒,這裏是關鍵點,其實引入管道模擬多線程的關鍵就是爲了這個暫停的3秒(實際上是微大於3秒的),讓系統有個緩衝的時間,起到限制所謂併發的進程數量。
echo >&6 # 再次往管道文件中寫入一個空行,爲了掛起的for循環能夠繼續執行。
} & #注意這裏要放到後臺,也就是說echo $i這個你的應用有13個是幾乎同時在後臺執行的,爲什麼是13個呢?當for循環完13次後會掛起,因爲管道已經空了,read -u6會暫時掛起,直到管道中有了空行。由於本commands block(即{}中的內容)是放在後臺執行的,可以理想的看成這13次循環是同時執行的,大家同時sleep了3秒,而又同時向管道中輸入了空行,所以管道在讀空之後差不多3秒的時候又瞬時多了13次空行,使得read -u6能夠繼續讀行,for循環得以繼續執行。
pid=$! # $!代表在後臺執行的最後一個進程。
echo $pid #打印最後一個進入後臺的子進程id;打印的pid結果印證了非併發,非多線程。
done
wait #等到後臺的進程都執行完畢。
exec 6>&- #刪除文件描述符6
exit 0