執行並行執行緒的 Bash 腳本在幾個小時內顯著減慢
我想出了一個腳本,它只是在後台執行另一個程序,試圖控制正在執行的最大程序數(在本例中為 300)。
它最初執行腳本的時間約為。1-2 毫秒,但在執行幾個小時後,它最終會以線性斜率減速到 200 毫秒 - 350 毫秒 execs。我使用數組來維護 PID#,但也取消設置減小表大小的鍵,但我有一種感覺,那就是罪魁禍首。
#!/bin/bash threads=() threadcounter=0 crd=0; while true; do threadcounter=${#threads[@]} crdcounter=${#crds[@]} if [ "$threadcounter" -lt 300 ] then s1=$(($(date +%s%N)/1000000)) pidf=$(/opt/remi/php/root/usr/bin/php cli.php initformula $crd >> /tmp/logger) & pidfid=$! s2=$(($(date +%s%N)/1000000)) echo "Init " $crd $(expr $s2 - $s1 ) "ms" threads[$pidfid]+=$pidfid else for pid in "${!threads[@]}"; do if [ ! -d "/proc/${pid}" ] then unset threads[$pid] fi done; fi; if [ "$crd" -gt 9999999 ] then echo "completed all"; exit; fi; crd=$(expr $crd + 1) done;
原始程式碼。
當您開始時,您只需開始 300 份
cli.php
. 這需要大約 1200 個程序,因為您要測量啟動所用的時間。
crd
然後將變數從 300循環到 9999999。
- 如果 shell 認為
threads
陣列中有備用插槽,它將cli.php
使用 4 個程序啟動一個新的。- 否則,您將循環大約 300 個程序,讓
核心填充
/proc
虛擬文件系統,並測試目錄是否存在。任何缺少的目錄都將導致條目從陣列中
刪除。
threads
您有一個未使用的數組,名為
crds
.因為在最初的 300 之後,如果程序表中有空閒槽,則 crd 變數的每個循環都會創建 1 個新副本
cli.php
,但如果表已滿,則最多可以刪除 300 個,在執行結束時我們只知道cli.php
已經啟動了300 到大約 9,967,000 個程序,數量取決於您的機器速度、cli.php
執行時間以及機器上的負載。6 級的海量有很多需要優化的地方!一個經驗法則是,在現代機器上,在一個核心上啟動 1 個程序需要 1ms,所以在初始啟動速度上你的表現並不差。一旦您用完可用核心來啟動新程序,我預計啟動率會出現顯著變化。
改進
一種加快速度的方法是使用
! kill -0 $pid
而不是[ ! -d "/proc/${pid}" ]
-kill -0
不會殺死任何東西,但如果程序不存在則返回錯誤。kill
是一個內置的外殼(原樣[
),但核心必須做的工作量較小。threads
如果大多數時候陣列中沒有空閒插槽,這將是最有效的。第二個改進是
expr
使用內置$(( ... ))
算法替換對外部程序的呼叫,從而減少啟動cli.php
.labels
如果大多數時候陣列中有空閒插槽,這是最有效的。要進行更多分析,我們需要知道
cli.php
執行所需的大致時間,以及有多少次執行。正如
BUGS
bash 手冊中的部分所說,It's too big and too slow.
在 bash 中改進數組實現當然是有可能的。替代實現
製作
在評論中建議使用
xargs
orparallel
。我經常喜歡使用make
. 首先要確定需要多少份cli.php
。然後一個簡單的Makefile
如%: \t/opt/remi/php/root/usr/bin/php cli.php initformula $@
其中 \t 是製表符。(這個簡單的版本假設您沒有任何數字名稱在 0 到 9999999 範圍內的文件)。然後呼叫 make as
make -O -j 300 $(seq 0 9999999) > /tmp/logger
如果您想要完整的 10,000,000 次 cli.php 呼叫。如果 cli.php 返回錯誤,我更喜歡
make
包括xargs
不需要採取過多步驟來中止處理的原因。xargs
如需
xargs
解決方案,請嘗試seq 0 9999999 | xargs -n 1 -P 300 /opt/remi/php/root/usr/bin/php cli.php initformula > /tmp/logger
這更簡單。
重擊
但是,使用
wait -nf
並且根本不擔心跟踪 PID 的 Bash 解決方案可能更符合 OP 的口味。它啟動最初的 300 個程序,然後當它檢測到其中一個完成時,它將啟動另一個。一旦開始了第 10,000,000 個任務,它就會進行最後的等待,讓所有工作都完成。不完全相同的算法,但非常接近。#!/bin/bash for(crd=0;crd<300;crd++); do /opt/remi/php/root/usr/bin/php cli.php initformula $crd & done > /tmp/logger for(;crd<=9999999;crd++); do wait -fn /opt/remi/php/root/usr/bin/php cli.php initformula $crd & done >> /tmp/logger wait