2020年6月22日 星期一

Bash 程式設計教學:平行執行背景子行程,用 wait 等待工作結束


本篇介紹在 Bash shell 中如何使用 wait 等待背景子行程的執行,並取回每個行程執行結果。
在 shell 程式設計中,為了讓程式執行起來更有效率,有時會讓多個子行程(subprocess)以 spawn 的方式放在背景執行,平行處理多項不同的工作,通常將需要等待硬碟 I/O 或網路回應的工作放在背景,可以程式執行的速度加快很多。
而在產生多個子行程之後,我們通常還會需要在程式的某處等待這些工作完成,並且取回執行結果,檢查每個工作是否有執行成功,這個部份就可以使用 wait

使用 wait 等待子行程(LibreOffice 原始檔
以下是一些典型的使用範例程式碼。

wait 等待子行程結束

wait 最基本的作用就是等待所有的子行程都結束,然後才繼續執行之後的工作:
#!/bin/bash
echo "Start"

# 平行處理多個工作
sleep 3 && echo "Job 1 is done" &
sleep 2 && echo "Job 2 is done" &
sleep 1 && echo "Job 3 is done" &

# 等待所有工作完成
wait

echo "Done"
這裡我放了三個測試用的指令,讓它們平行在背景執行,當每個工作完成時會輸出一行訊息,我故意安排讓執行時間比較久的工作先執行,觀察它們完成的順序。
執行之後,結果如下:
Start
Job 3 is done
Job 2 is done
Job 1 is done
Done
這裡的 Job 1 雖然是第一個執行的,但因為它需要最常的執行時間,所以最慢完成,wait 會在所有工作都結束之後,才繼續執行後續的指令。

取得子行程執行結果

放在背景執行的工作若處理結果不如預期,可能就會影響以後的程式執行,所以在子行程結束之後,通常都需要檢查一下其傳回值,確認是否每個工作都有正常完成。
wait 指令可以在其參數中指定要等待的子行程 ID,這樣的話 wait 就會等待至指定的子行程執行結束,並傳回該子行程的傳回值,以下是一個簡單的範例:
#!/bin/bash
sleep 2 && true &    # 成功
#sleep 2 && false &  # 失敗
PID=$!     # 取得上一個背景行程的 ID
wait $PID  # 等待背景行程執行完畢

# 檢查背景行程的傳回值
if [ $? -eq 0 ]; then
  echo "Success."
else
  echo "Fail!"
fi
Success.
這個範例將 sleep 放在背景執行,然後靠著 Bash 的特殊變數 $! 取得這個背景行程的行程 ID,並將這個行程 ID 傳給 wait,等待這個背景工作執行完畢,最後再從 Bash 的特殊變數 $? 取得 wait 的傳回值(也就是子行程的傳回值),檢查這個背景工作是否有執行成功。

取得多個子行程執行結果

以下是一個比較複雜一點的範例,在背景工作結束之後,會計算失敗的工作數,判斷所有工作是否都正常完成。
#!/bin/bash
# 計算失敗的工作
FAIL=0

# 平行處理多個工作
sleep 3 && echo "Job 1 is done" && true &  # 成功
sleep 2 && echo "Job 2 is fail" && false & # 失敗
sleep 1 && echo "Job 3 is done" && true &  # 成功

# 對每一個子行程執行 wait
for job in `jobs -p`
do
  echo "Wait job: ${job}"
  wait $job || let FAIL+=1
done

# 檢查失敗工作數
if [ $FAIL -eq 0 ]; then
  echo "All jobs are done."
else
  echo "${FAIL} jobs fail!"
fi
Wait job: 3598
Job 3 is done
Job 2 is fail
Job 1 is done
Wait job: 3599
Wait job: 3600
1 jobs fail!
這個範例跟上一個大同小異,不過我們同時執行好幾個背景行程,然後使用 jobs -p 取得所有背景行程的行程 ID,放進 for 迴圈中逐一呼叫 wait,並將執行失敗的背景行程數目記錄下來。
參考資料:StackOverflow

沒有留言: