这是@Luca Tettamanti 对最受好评的答案的扩展,以制作一个完全可运行的示例。
这个答案让我想知道:
是什么类型的变量n_procs
,它包含什么?是什么类型的变量procs
,它包含什么?有人可以通过为这些变量添加定义来更新此答案以使其可运行吗?我不明白怎么做。
...并且:
- 子进程完成后如何从子进程中获取返回码(这是这个问题的全部症结所在)?
无论如何,我想通了,所以这是一个完全可运行的示例。
笔记:
$!
是如何获取最后执行的子进程的PID(进程ID)。
- 运行任何带有
&
其后的命令cmd &
,例如,使其在后台作为与主进程的并行 suprocess 运行。
myarray=()
是如何在 bash 中创建数组。
- 要了解有关
wait
内置命令的更多信息,请参阅help wait
. 另请参阅,尤其是有关作业控制内置程序的官方 Bash 用户手册,例如wait
和jobs
,此处:https ://www.gnu.org/software/bash/manual/html_node/Job-Control-Builtins.html#索引等待。
完整的、可运行的程序:等待所有进程结束
multi_process_program.sh(来自我的eRCaGuy_hello_world 存储库):
#!/usr/bin/env bash
# This is a special sleep function which returns the number of seconds slept as
# the "error code" or return code" so that we can easily see that we are in
# fact actually obtaining the return code of each process as it finishes.
my_sleep() {
seconds_to_sleep="$1"
sleep "$seconds_to_sleep"
return "$seconds_to_sleep"
}
# Create an array of whatever commands you want to run as subprocesses
procs=() # bash array
procs+=("my_sleep 5")
procs+=("my_sleep 2")
procs+=("my_sleep 3")
procs+=("my_sleep 4")
num_procs=${#procs[@]} # number of processes
echo "num_procs = $num_procs"
# run commands as subprocesses and store pids in an array
pids=() # bash array
for (( i=0; i<"$num_procs"; i++ )); do
echo "cmd = ${procs[$i]}"
${procs[$i]} & # run the cmd as a subprocess
# store pid of last subprocess started; see:
# https://unix.stackexchange.com/a/30371/114401
pids+=("$!")
echo " pid = ${pids[$i]}"
done
# OPTION 1 (comment this option out if using Option 2 below): wait for all pids
for pid in "${pids[@]}"; do
wait "$pid"
return_code="$?"
echo "PID = $pid; return_code = $return_code"
done
echo "All $num_procs processes have ended."
通过运行将上面的文件更改为可执行chmod +x multi_process_program.sh
,然后像这样运行它:
time ./multi_process_program.sh
样本输出。查看time
调用中命令的输出如何显示运行时间为 5.084 秒。我们还能够成功地从每个子流程中检索返回码。
eRCaGuy_hello_world/bash$ time ./multi_process_program.sh
num_procs = 4
cmd = my_sleep 5
pid = 21694
cmd = my_sleep 2
pid = 21695
cmd = my_sleep 3
pid = 21697
cmd = my_sleep 4
pid = 21699
PID = 21694; return_code = 5
PID = 21695; return_code = 2
PID = 21697; return_code = 3
PID = 21699; return_code = 4
All 4 processes have ended.
PID 21694 is done; return_code = 5; 3 PIDs remaining.
PID 21695 is done; return_code = 2; 2 PIDs remaining.
PID 21697 is done; return_code = 3; 1 PIDs remaining.
PID 21699 is done; return_code = 4; 0 PIDs remaining.
real 0m5.084s
user 0m0.025s
sys 0m0.061s
更进一步:确定每个单独流程何时结束
如果您想在每个进程完成时执行一些操作,并且您不知道它们何时完成,您可以在无限while
循环中轮询以查看每个进程何时终止,然后执行您想要的任何操作。
只需注释掉上面的“OPTION 1”代码块,并用这个“OPTION 2”块代替它:
# OR OPTION 2 (comment out Option 1 above if using Option 2): poll to detect
# when each process terminates, and print out when each process finishes!
while true; do
for i in "${!pids[@]}"; do
pid="${pids[$i]}"
# echo "pid = $pid" # debugging
# See if PID is still running; see my answer here:
# https://stackoverflow.com/a/71134379/4561887
ps --pid "$pid" > /dev/null
if [ "$?" -ne 0 ]; then
# PID doesn't exist anymore, meaning it terminated
# 1st, read its return code
wait "$pid"
return_code="$?"
# 2nd, remove this PID from the `pids` array by `unset`ting the
# element at this index; NB: due to how bash arrays work, this does
# NOT actually remove this element from the array. Rather, it
# removes its index from the `"${!pids[@]}"` list of indices,
# adjusts the array count(`"${#pids[@]}"`) accordingly, and it sets
# the value at this index to either a null value of some sort, or
# an empty string (I'm not exactly sure).
unset "pids[$i]"
num_pids="${#pids[@]}"
echo "PID $pid is done; return_code = $return_code;" \
"$num_pids PIDs remaining."
fi
done
# exit the while loop if the `pids` array is empty
if [ "${#pids[@]}" -eq 0 ]; then
break
fi
# Do some small sleep here to keep your polling loop from sucking up
# 100% of one of your CPUs unnecessarily. Sleeping allows other processes
# to run during this time.
sleep 0.1
done
完整程序的示例运行和输出,其中选项 1 已注释掉且选项 2 正在使用中:
eRCaGuy_hello_world/bash$ ./multi_process_program.sh
num_procs = 4
cmd = my_sleep 5
pid = 22275
cmd = my_sleep 2
pid = 22276
cmd = my_sleep 3
pid = 22277
cmd = my_sleep 4
pid = 22280
PID 22276 is done; return_code = 2; 3 PIDs remaining.
PID 22277 is done; return_code = 3; 2 PIDs remaining.
PID 22280 is done; return_code = 4; 1 PIDs remaining.
PID 22275 is done; return_code = 5; 0 PIDs remaining.
PID XXXXX is done
在该过程终止后,这些行中的每一行都会立即打印出来!请注意,尽管sleep 5
(在本例中为 PID 22275
)的进程首先运行,但它最后完成,并且我们在每个进程终止后立即成功检测到它。我们还成功地检测到了每个返回码,就像在选项 1 中一样。
其他参考:
*****+ [非常有帮助]获取后台进程的退出代码- 这个答案教会了我关键原则(强调添加):
wait <n>
等到带有 PID 的进程完成(它会阻塞直到进程完成,所以你可能不想在确定进程完成之前调用它),然后返回已完成进程的退出代码。
换句话说,它帮助我知道即使在该过程完成后,您仍然可以调用wait
它来获取它的返回码!
如何检查进程ID(PID)是否存在
- 我的答案
从 Bash 数组中删除一个元素- 请注意,bash 数组中的元素实际上并没有被删除,它们只是“未设置”。请参阅我在上面代码中的评论以了解这意味着什么。
如何使用命令行可执行文件true
在 bash 中进行无限循环:https ://www.cyberciti.biz/faq/bash-infinite-loop/