shell 多線程實現

轉自:https://blog.csdn.net/dubendi/article/details/78931979

需求:併發檢測1000臺web服務器狀態(或者併發爲1000臺web服務器分發文件等)如何用shell實現?

方案一:(這應該是大多數人都第一時間想到的方法吧)

思路:一個for循環1000次,順序執行1000次任務。

實現:
複製代碼

#!/bin/bash
start_time=`date +%s` #定義腳本運行的開始時間

for ((i=1;i<=1000;i++))
do
sleep 1 #sleep 1用來模仿執行一條命令需要花費的時間(可以用真實命令來代替)
echo ‘success’$i;
done

stop_time=`date +%s` #定義腳本運行的結束時間

echo “TIME:`expr $stop_time - $start_time`”

複製代碼

運行結果:
複製代碼

[root@iZ94yyzmpgvZ ~]# . test.sh
success1
success2
success3
success4
success5
success6
success7
…此處省略
success999
success1000
TIME:1000

複製代碼

代碼解析以及問題:

一個for循環1000次相當於需要處理1000個任務,循環體用sleep 1代表運行一條命令需要的時間,用success$i來標示每條任務.

這樣寫的問題是,1000條命令都是順序執行的,完全是阻塞時的運行,假如每條命令的運行時間是1秒的話,那麼1000條命令的運行時間是1000秒,效率相當低,而我的要求是併發檢測1000臺web的存活,如果採用這種順序的方式,那麼假如我有1000臺web,這時候第900臺機器掛掉了,檢測到這臺機器狀態所需要的時間就是900s!

所以,問題的關鍵集中在一點:如何併發

方案二:

思路:一個for循環1000次,循環體裏面的每個任務都放入後臺運行(在命令後面加&符號代表後臺運行)。

實現:
複製代碼

#!/bin/bash
start=`date +%s` #定義腳本運行的開始時間

for ((i=1;i<=1000;i++))
do
{
sleep 1 #sleep 1用來模仿執行一條命令需要花費的時間(可以用真實命令來代替)
echo ‘success’$i;
}& #用{}把循環體括起來,後加一個&符號,代表每次循環都把命令放入後臺運行
#一旦放入後臺,就意味着{}裏面的命令交給操作系統的一個線程處理了
#循環了1000次,就有1000個&把任務放入後臺,操作系統會併發1000個線程來處理
#這些任務
done
wait #wait命令的意思是,等待(wait命令)上面的命令(放入後臺的)都執行完畢了再
#往下執行。
#在這裏寫wait是因爲,一條命令一旦被放入後臺後,這條任務就交給了操作系統
#shell腳本會繼續往下運行(也就是說:shell腳本里面一旦碰到&符號就只管把它
#前面的命令放入後臺就算完成任務了,具體執行交給操作系統去做,腳本會繼續
#往下執行),所以要在這個位置加上wait命令,等待操作系統執行完所有後臺命令
end=`date +%s` #定義腳本運行的結束時間

echo “TIME:`expr $end - $start`”

複製代碼

運行結果:
複製代碼

[root@iZ94yyzmpgvZ /]# . test1.sh

[989] Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '}' at position 4: i; }̲ [990] Done …i; }
success992
[991] Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '}' at position 4: i; }̲ [992] Done …i; }
success993
[993] Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '}' at position 4: i; }̲ success994 suc…i; }
success996
[995] Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '}' at position 4: i; }̲ [996] Done …i; }
success997
success998
[997] Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '}' at position 4: i; }̲ success999 [99…i; }
[999]- Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '}' at position 4: i; }̲ success1000 [1…i; }
TIME:2

複製代碼

代碼解析以及問題:

shell中實現併發,就是把循環體的命令用&符號放入後臺運行,1000個任務就會併發1000個線程,運行時間2s,比起方案一的1000s,已經非常快了。

可以看到輸出結果success4 …success3完全都是無序的,因爲大家都是後臺運行的,這時候就是cpu隨機運行了,所以並沒有什麼順序

這樣寫確實可以實現併發,然後,大家可以想象一下,1000個任務就要併發1000個線程,這樣對操作系統造成的壓力非常大,它會隨着併發任務數的增多,操作系統處理速度會變慢甚至出現其他不穩定因素,就好比你在對nginx調優後,你認爲你的nginx理論上最大可以支持1w併發了,實際上呢,你的系統會隨着高併發壓力會不斷攀升,處理速度會越來越慢(你以爲你扛着500斤的東西你還能跑的跟原來一樣快嗎)

方案三:

思路:基於方案二,使用linux管道文件特性製作隊列,控制線程數目

知識儲備:

一.管道文件

1:無名管道(ps aux | grep nginx)

2:有名管道(mkfifo /tmp/fd1)

有名管道特性:

1.cat /tmp/fd1(如果管道內容爲空,則阻塞)

實驗:

2.echo “test” > /tmp/fd1(如果沒有讀管道的操作,則阻塞)

總結:

利用有名管道的上述特性就可以實現一個隊列控制了

你可以這樣想:一個女士公共廁所總共就10個蹲位,這個蹲位就是隊列長度,女廁 所門口放着10把藥匙,要想上廁所必須拿一把藥匙,上完廁所後歸 還藥匙,下一個人就可以拿藥匙進去上廁所了,這樣同時來了1千

位美女上廁所,那前十個人搶到藥匙進去上廁所了,後面的990人 需要等一個人出來歸還藥匙纔可以拿到藥匙進去上廁所,這樣10把藥匙就實現了控制1000人上廁所的任務(os中稱之爲信號量)

二.文件描述符

1.管道具有存一個讀一個,讀完一個就少一個,沒有則阻塞,放回的可以重複取,這正是隊列特性,但是問題是當往管道文件裏面放入一段內容,沒人取則會阻塞,這樣你永遠也沒辦法往管道里面同時放入10段內容(想當與10把藥匙),解決這個問題的關鍵就是文件描述符了。

  1. mkfifo /tmp/fd1

創建有名管道文件exec 3<>/tmp/fd1,創建文件描述符3關聯管道文件,這時候3這個文件描述符就擁有了管道的所有特性,還具有一個管道不具有的特性:無限存不阻塞,無限取不阻塞,而不用關心管道內是否爲空,也不用關心是否有內容寫入引用文件描述符: &3可以執行n次echo >&3 往管道里放入n把鑰匙

exec命令用法:http://blog.sina.com.cn/s/blog_7099ca0b0100nby8.html

實現:

複製代碼

#!/bin/bash
start_time=`date +%s` #定義腳本運行的開始時間
[ -e /tmp/fd1 ] || mkfifo /tmp/fd1 #創建有名管道
exec 3<>/tmp/fd1 #創建文件描述符,以可讀(<)可寫(>)的方式關聯管道文件,這時候文件描述符3就有了有名管道文件的所有特性
rm -rf /tmp/fd1 #關聯後的文件描述符擁有管道文件的所有特性,所以這時候管道文件可以刪除,我們留下文件描述符來用就可以了
for ((i=1;i<=10;i++))
do
echo >&3 #&3代表引用文件描述符3,這條命令代表往管道里面放入了一個"令牌"
done

for ((i=1;i<=1000;i++))
do
read -u3 #代表從管道中讀取一個令牌
{
sleep 1 #sleep 1用來模仿執行一條命令需要花費的時間(可以用真實命令來代替)
echo ‘success’$i
echo >&3 #代表我這一次命令執行到最後,把令牌放回管道
}&
done
wait

stop_time=`date +%s` #定義腳本運行的結束時間

echo “TIME:`expr $stop_time - $start_time`”
exec 3<&- #關閉文件描述符的讀
exec 3>&- #關閉文件描述符的寫

複製代碼

運行結果:
複製代碼

[root@iZ94yyzmpgvZ /]# . test2.sh
success4
success6
success7
success8
success9
success5

success935
success941
success942

success992
[992] Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '&' at position 11: i; echo 1>&̲3; } success993…i; echo 1>&3; }
success994
[994] Done { sleep 1; echo 'success’KaTeX parse error: Expected 'EOF', got '&' at position 11: i; echo 1>&̲3; } success998…i; echo 1>&3; }
TIME:101

複製代碼

代碼解析以及問題:

兩個for循環,第一個for循環10次,相當於在女士公共廁所門口放了10把鑰匙,第二個for

循環1000次,相當於1000個人來上廁所,read -u3相當於取走一把藥匙,{}裏面最後一行代碼echo >&3相當於上完廁所送還藥匙。

這樣就實現了10把藥匙控制1000個任務的運行,運行時間爲101s,肯定不如方案二快,但是比方案一已經快很多了,這就是隊列控制同一時間只有最多10個線程的併發,既提高了效率,又實現了併發控制。

注意:創建一個文件描述符exec 3<>/tmp/fd1 不能有空格,代表文件描述符3有可讀(<)可寫(>)權限,注意,打開的時候可以寫在一起,關閉的時候必須分開關,exec 3<&-關閉讀,exec 3>&-關閉寫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章