提问人:faceless 提问时间:11/16/2023 更新时间:11/20/2023 访问量:146
并行化函数的最后一个实例的状态
Status of the last instance of a parallelized function
问:
我正在寻找一种并行运行函数的方法,并确切地知道所有函数实例何时完成。 我添加了一个微调器(函数的实例运行时间不同,取决于变量,所以我需要在屏幕上看到一些东西)并按以下方式尝试:
for a in "${ARRAY[@]}"; do
spin='/-\|'
while true; do
i=$(( (i+1) %4 )); printf "\r[ ${spin:$i:1} ] "; sleep .3;
done & someFunction &
kill $!; trap 'kill $!' SIGTERM
done
但不起作用。我假设是因为如果它被触发,它会立即被下一行代码杀死。而且,在脚本结束后,微调器会永远在 CLI 上工作。someFunction
someFunction &
同时运行,等到最后一个 instace 完成,并保持微调器直到那时的方法是什么?someFunction
答:
0赞
Andrej Podzimek
11/16/2023
#1
您需要跟踪已分叉的流程,然后为它们分叉。该命令非常灵活,还可以一次(而不是一个接一个)或仅等待特定作业。请参见 部分。以下脚本启动一个微调器,该微调器每秒打印一次内容,然后分叉出 5 个休眠时间不同的进程。wait
wait
man bash
SHELL BUILTIN COMMANDS
spinner() { for ((;;)); do sleep 1; printf '<spin>'; done; }
someFunction() { sleep "$1"; }
spinner &
spinner_pid="$!"
for ((i = 2; i < 12; i += 2)); do
someFunction "$i"&
function_pids["$!"]="$i"
done
while ((${#function_pids[@]})); do
wait -n -p pid && echo -n 'finished: ' || echo -n 'failed: '
echo "${function_pids[pid]}"
unset 'function_pids[pid]'
done
kill -TERM "$spinner_pid"
wait -n "$spinner_pid" || :
1赞
pjh
11/17/2023
#2
由于 Bash 管理后台进程的方式,通常通过 PID 终止进程会杀死错误进程的可能性很小。如果您使用的是 Bash 4.3(2014 年发布)或更高版本,请尝试以下免杀代码:
#! /bin/bash -p
# FIXME: Define the 'args' array and the 'someFunction' function
# Run a spinner as a coprocess
coproc spinner \
{
declare -r spin='/-\|'
declare i=0
while read -r -t 0.3 _; (( $? > 128 )); do
printf '\r[ %s ] ' "${spin:i++%4:1}" >&2
done
}
# Disown the spinner coprocess so 'wait' will not wait for it to exit
disown %%
for a in "${args[@]}"; do
someFunction "$a" &
done
# Wait for all background processes (except the disowned spinner) to exit
wait
# Close the pipe to the spinner. It will read EOF and close.
exec {spinner[1]}>&-
- 将微调器作为协同进程运行,可以通过关闭其输入管道而不是终止它来停止它。它还使得取消微调器进程的所有权,因此等待不会等待它退出。
- 如读取文档中所述,如果在 0.3 秒内未能读取输入行,则将返回大于 128 的状态。在微调器中使用 instead 而不是 for 延迟可以避免为每个延迟运行子进程,从而节省资源。
read -r -t 0.3 _
read
sleep
exec {spinner[1]}>&-
关闭到协进程的管道(写入端)。这将导致 in 协同进程返回状态小于 128,从而导致协同进程立即退出。如果没有显式关闭管道,协同进程将继续运行,直到顶级进程退出(此时操作系统将自动关闭管道)。(因此,如果顶级进程在 之后立即退出,则不需要显式关闭,但无论如何我都会这样做,以便以后添加更多代码(或将代码粘贴到更大的程序中)。read
wait
exec {spinner[1]}>&-
不适用于早于 4.3 的 Bash 版本。解决 Bash 版本 4.0 问题的一种方法是将其替换为 .最好避免使用 (请参阅为什么在 Bash 中应该避免使用 eval,以及我应该使用什么来代替?)),但我不知道在这种情况下适用于 Bash 4.0 的替代方案。eval "exec ${spinner[1]}>&-"
eval
- 最好避免使用ALL_UPPERCASE变量名称,因为存在与 shell 编程中使用的大量特殊ALL_UPPERCASE变量发生冲突的危险。请参阅正确的 Bash 和 shell 脚本变量大小写。这就是为什么我在原始代码中用 .
ARRAY
args
评论
0赞
xpusostomos
11/18/2023
我认为否认一个进程,只是为了你可以对其他进程进行分组是不好的做法。如果有人以后想杀死集群,它就会自行走开。有更好的方法可以实现这一目标。
0赞
pjh
11/18/2023
@xpusostomos,如果父进程因任何原因退出,则通向协进程的管道将自动关闭,微调器将停止。它不能自己徘徊。
0赞
xpusostomos
11/18/2023
我现在明白你在做什么了。微妙。还。。。你依赖于微调器进程实际正常运行,以便读取、获取 EOF,然后自行死亡,而正确地,操作系统使用子进程机制来杀死子进程机制,即使他们被冻结和行为不端。而且没有必要否认这个问题。
0赞
faceless
11/18/2023
@pjh - 尽管在稍作调整后所有其他给定的方式都可以工作,但我发现您的方式更优雅、更合适。它也有效(谢谢!但是有一个问题 - 每当我在一个小测试函数上尝试这段代码时 - 效果很好。但是当我在 prod 函数上做 - 函数本身时,微调器的旋转效果很好,但最终它会抛出这个错误语句的行。除了这种视觉上的厌恶,它起作用了。我想知道这个错误的原因是什么,可能有什么影响,以及如何检测根本原因?exec: {spinner[1]}: not found
0赞
xpusostomos
11/19/2023
@faceless我不知道,但我会认为最终的 exec {spinner[1]}>&- 是多余的,因为在那之后进程退出,并且进程拥有的所有文件描述符在退出时都会关闭。因此,删除那条线似乎是可以尝试的事情。
1赞
xpusostomos
11/17/2023
#3
我想每个人都想多了......
(
for a in "${ARRAY[@]}"; do
someFunction &
done
wait
) &
GRP=$!
spin='/-\|'
while true
do
i=$(( (i+1) %4 )); printf "\r[ ${spin:$i:1} ] "; sleep .3;
done &
SPINNER=$!
wait $GRP
kill $SPINNER
评论
0赞
xpusostomos
11/17/2023
我给出了上述内容作为对原始代码的简单更正。但是,我发表评论说,它天真地假设您不会按 ^C 让微调器在后台运行。使用 bash “trap” 进行清理是个好主意,以防用户点击 ^C
0赞
xpusostomos
11/18/2023
在输出微调器之前,最好使用“test -t 1”来检查您是否在 TTY 上运行。
0赞
S. Mondal
11/18/2023
#4
只需将“sleep 10”替换为您的程序,
#!/bin/bash
text='/-\|'
delay=".5"
spinner() {
PID=$!
echo "Please Wait..."
while [ -d /proc/$PID ]; do
for i in $(seq 0 3) ; do
printf "\r[ ${text:$i:1} ]"
sleep ${delay}
done
done
}
mainprogram()
{
sleep 10
}
mainprogram &
spinner
echo ""
评论
0赞
xpusostomos
11/18/2023
特定于 Linux,不可移植。也无法回答关于启动一组进程并监视所有进程的问题。
评论
wait
$!
&