微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么等待完成的父 shell 进程无法可靠地接收从 Bash 脚本中的后台作业发送的 USR1 信号?

如何解决为什么等待完成的父 shell 进程无法可靠地接收从 Bash 脚本中的后台作业发送的 USR1 信号?

我有一个 Bash 脚本并行运行一堆后台作业。 在某些条件下,在后台作业完成之前,它会发送 向生成的 Bash 进程发送 USR1 信号(例如,通知 作为作业的一部分运行的某些进程已终止 非零退出代码)。

在简化形式中,该脚本等效于如下所示的脚本。 在这里,为了简单起见,每个后台作业总是发送一个 USR1 信号 在完成之前,无条件地(通过 signalparent() 函数)。

signalparent() { kill -USR1 $$; }
handlesignal() { echo 'USR1 signal caught' >&2; }
trap handlesignal USR1

for i in {1..10}; do
    {
        sleep 1
        echo "job $i finished" >&2
        signalparent
    } &
done
wait

当我运行上述脚本时(至少在 macOS 11.1 上使用 Bash 3.2.57), 我观察到一些我无法解释的行为,这让我思考 Bash 作业管理和 我忽略的信号捕获。

具体来说,我想获得以下解释 行为。

  1. 几乎总是,当我运行脚本时,我看到更少的“信号捕获” 输出中的行(来自 handlesignal() 函数)比那里 是在 for 循环中启动的作业——大部分时间是 为正在启动的 10 个作业打印的其中 4 行。

    为什么在 wait 调用完成时,有 仍然是后台作业,其信号 kill 命令具有 尚未执行?

  2. 同时,每隔一段时间,在一些脚本调用中, 我观察到 kill 命令(来自 signalparent() 函数) 报告有关运行脚本的原始进程的错误 (即,带有 $$ PID 的那个)不再存在——参见 输出如下。

    为什么有些作业的信号 kill 命令仍然存在 在父 shell 进程已经终止时运行? 我的理解是父母不可能 进程在所有后台作业之前终止,因为 wait 调用

    job 2 finished
    job 3 finished
    job 5 finished
    job 4 finished
    job 1 finished
    job 6 finished
    USR1 signal caught
    USR1 signal caught
    job 10 finished
    job 7 finished
    job 8 finished
    job 9 finished
    bash: line 3: kill: (19207) - No such process
    bash: line 3: kill: (19207) - No such process
    bash: line 3: kill: (19207) - No such process
    bash: line 3: kill: (19207) - No such process
    

这两种行为都向我表明存在竞争条件 某种,我不太明白它的起源。我会 不胜感激,如果有人能在这些方面启发我,甚至可能 建议如何更改脚本以避免此类竞争条件。

解决方法

这在 Bash Reference Manual 中解释如下。

当 bash 通过 wait 内置函数等待异步命令时,接收到设置了陷阱的信号将导致 wait 内置函数立即返回,退出状态大于128,紧接着执行陷阱。

因此,您需要重复 wait 直到它返回 0 以确保所有后台作业都已终止,例如:

until wait; do
    :
done

据我所知,由于 wait 调用,父进程不可能在所有后台作业完成之前终止。

那是个误会; wait 可能会在后台运行作业时接收到设置了陷阱的信号而返回,这可能导致程序正常完成,副作用是使这些作业成为孤立的。

,

关于‘几乎总是,当我运行脚本时,我在输出中看到更少的“信号捕获”行’

根据signal(7)

标准信号不排队。如果一个标准信号的多个实例在该信号被阻塞时生成,那么只有一个信号实例被标记为未决(并且该信号在解除阻塞时只会被传递一次)。

更改脚本以使信号不会同时到达的一种方法如下:

signalparent() {
    kill -USR1 $$
}

ncaught=0
handlesignal() {
    (( ++ncaught ))
    echo "USR1 signal caught (#=$ncaught)" >&2
}
trap handlesignal USR1

for i in {1..10}; do
    {
        sleep $i
        signalparent
    } &
done

nwaited=0
while (( nwaited < 10 )); do
    wait && (( ++nwaited ))
done

这是在 macOS 10.15 上使用 Bash 5.1 修改后的脚本的输出:

USR1 signal caught (#=1)
USR1 signal caught (#=2)
USR1 signal caught (#=3)
USR1 signal caught (#=4)
USR1 signal caught (#=5)
USR1 signal caught (#=6)
USR1 signal caught (#=7)
USR1 signal caught (#=8)
USR1 signal caught (#=9)
USR1 signal caught (#=10)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。