并行化函数的最后一个实例的状态

Status of the last instance of a parallelized function

提问人:faceless 提问时间:11/16/2023 更新时间:11/20/2023 访问量:146

问:

我正在寻找一种并行运行函数的方法,并确切地知道所有函数实例何时完成。 我添加了一个微调器(函数的实例运行时间不同,取决于变量,所以我需要在屏幕上看到一些东西)并按以下方式尝试:

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 上工作。someFunctionsomeFunction &

同时运行,等到最后一个 instace 完成,并保持微调器直到那时的方法是什么?someFunction

Linux Bash Shell 脚本

评论

2赞 Poshi 11/16/2023
看一看。wait
2赞 F. Hauri - Give Up GitHub 11/16/2023
您必须每次使用后立即存储!!您可以在使用同时/并发文件传输加速 rsync 中阅读完整示例?$!&
1赞 pjh 11/17/2023
请参阅 ProcessManagement - Greg 的 Wiki,了解有关使用 Bash 管理后台进程的优秀信息。

答:

0赞 Andrej Podzimek 11/16/2023 #1

您需要跟踪已分叉的流程,然后为它们分叉。该命令非常灵活,还可以一次(而不是一个接一个)或仅等待特定作业。请参见 部分。以下脚本启动一个微调器,该微调器每秒打印一次内容,然后分叉出 5 个休眠时间不同的进程。waitwaitman bashSHELL 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 _readsleep
  • exec {spinner[1]}>&-关闭到协进程的管道(写入端)。这将导致 in 协同进程返回状态小于 128,从而导致协同进程立即退出。如果没有显式关闭管道,协同进程将继续运行,直到顶级进程退出(此时操作系统将自动关闭管道)。(因此,如果顶级进程在 之后立即退出,则不需要显式关闭,但无论如何我都会这样做,以便以后添加更多代码(或将代码粘贴到更大的程序中)。readwait
  • 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 脚本变量大小写。这就是为什么我在原始代码中用 .ARRAYargs

评论

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,不可移植。也无法回答关于启动一组进程并监视所有进程的问题。