为什么等待完成的父 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 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?