为什么等待完成的父 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)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...