Bash

執行並行執行緒的 Bash 腳本在幾個小時內顯著減慢

  • March 17, 2020

我想出了一個腳本,它只是在後台執行另一個程序,試圖控制正在執行的最大程序數(在本例中為 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執行所需的大致時間,以及有多少次執行。

正如BUGSbash 手冊中的部分所說,It's too big and too slow.在 bash 中改進數組實現當然是有可能的。

替代實現

製作

在評論中建議使用xargsor parallel。我經常喜歡使用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

引用自:https://unix.stackexchange.com/questions/573315