繁体   English   中英

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

[英]Why may USR1 signals sent from background jobs in a Bash script not be reliably received by the parent shell process waiting for their completion?

我有一个 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. 几乎总是,当我运行脚本时,我看到 output(来自handlesignal()函数)中的“信号捕获”行少于在for循环中启动的作业——大多数时候它是其中的一到四个为正在启动的十个作业打印的行数。

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

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

    为什么在父 shell 进程已经终止时,有哪些作业的信号kill命令仍在运行? 我的理解是,由于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 参考手册中对此进行了如下说明。

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

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

until wait; do
    :
done

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

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

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

根据信号(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 修改的脚本的 output:

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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM