shell佇列實現執行緒併發控制

2021-08-09 13:51:49 字數 3861 閱讀 1816

請看原文

需求:併發檢測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`"

**解析以及問題:

乙個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& #用{}把迴圈體括起來,後加乙個&符號,代表每次迴圈都把命令放入後台執行

#一旦放入後台,就意味著{}裡面的命令交給作業系統的乙個執行緒處理了

#迴圈了1000次,就有1000個&把任務放入後台,作業系統會併發1000個執行緒來處理

#這些任務

done

wait #wait命令的意思是,等待(wait命令)上面的命令(放入後台的)都執行完畢了再

#往下執行。

#在這裡寫wait是因為,一條命令一旦被放入後台後,這條任務就交給了作業系統

#shell指令碼會繼續往下執行(也就是說:shell指令碼裡面一旦碰到&符號就只管把它

#前面的命令放入後台就算完成任務了,具體執行交給作業系統去做,指令碼會繼續

#往下執行),所以要在這個位置加上wait命令,等待作業系統執行完所有後台命令

end=`date +%s` #定義指令碼執行的結束時間

echo

"time:`expr $end - $start`"

**解析以及問題:

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把藥匙),解決這個問題的關鍵就是檔案描述符了。

mkfifo /tmp/fd1

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

exec命令用法:

#!/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++))

doecho >&3

#&3代表引用檔案描述符3,這條命令代表往管道裡面放入了乙個"令牌"

done

for ((i=1;i<=1000;i++))

doread -u3 #代表從管道中讀取乙個令牌

&done

wait

stop_time=`date +%s` #定義指令碼執行的結束時間

echo

"time:`expr $stop_time - $start_time`"

exec

3<&- #關閉檔案描述符的讀

exec

3>&- #關閉檔案描述符的寫

**解析以及問題:

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

迴圈1000次,相當於1000個人來上廁所,read -u3相當於取走一把藥匙,{}裡面最後一行**echo >&3相當於上完廁所送還藥匙。

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

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

本文**

使用Shell實現併發控制

思路就是建立乙個管道,往裡面寫入固定行數的資料,當程序進行操作之前先讀取一行,執行完操作寫入一行。即可實現可控程序數的併發操作。注意這個思路和訊號量的概念類似。usr bin bash ping02 thread 8 tmp fifofile tmp fifo mkfifo tmp fifofile...

多執行緒併發佇列實現

2.使用 wait notify 實現乙個佇列,佇列有2個方法,add 和 get add方法往佇列中新增元素,get方法往佇列中獲得元素。佇列必須是執行緒安全的。如果get執行時,隊列為空,執行緒必須阻塞等待,直到有佇列有資料。如果add時,佇列已經滿,則add執行緒要等待,直到佇列有空閒空間。1...

命名管道Shell併發執行緒控制

原始 如下 bin bash for i in seq 255 do ping c 1 192.168.182.i dev null 2 1 eq 0 echo done 這個指令碼使用乙個執行緒阻塞執行了255次迴圈,每次迴圈執行一秒,掃瞄完需要用255s,效率是十分低的。改進如下 bin bas...