如何在python中杀死一个分叉的孩子及其jackd子进程

Posted

技术标签:

【中文标题】如何在python中杀死一个分叉的孩子及其jackd子进程【英文标题】:How to kill a forked child and its jackd subprocess in python 【发布时间】:2018-03-26 19:32:23 【问题描述】:

我正在尝试实现一个托盘图标,它可以让我控制一个进程(jackd)。最值得注意的是

我想读取进程 stdout 和 stderr 以进行日志记录 我希望能够从主程序的菜单项中终止进程

杀戮让我头疼。我相信,当我阅读 stdout 和 stderr 时,我必须在额外的线程/进程中执行此操作以保持图标响应。所以我使用 fork() 创建了一个子进程并在子进程上调用了setsid()。然后该子进程使用subprocess 模块调用jackd

from subprocess import PIPE, STDOUT, Popen
def runAndLog(self, cmd):
    po = Popen(cmd, stdout=PIPE, stderr=STDOUT)
    line = po.stdout.readline()
    while line:
       print(line.rstrip())
       line = po.stdout.readline()
 self.runAndLog(["jackd","-R", "-P80", "-t5000", "-dfirewire", "-r44100", "-p256", "-n3"])

现在我希望我可以让我的分叉子进程进入同一个进程组,这样我就可以一次杀死两者,但令我惊讶的是Popen 创建了一个新进程组和一个新会话:

  PID  PPID  PGID   SID COMMAND
 5790  5788  5788 31258 python (parent, main)
 5791  5790  5791  5791 python (forked child after setsid - ok)
 5804  5791  5804  5804 jackd  (grandchild created by Popen)

因此,杀死孩子不会杀死孙子,我看不出有任何干净的方法可以让main 知道孙子的 PID (5804) 以明确地杀死它。我可能会使用一些信号诡计让垂死的孩子杀死孙子,但我犹豫不决。

我也不能在 jackd 进程上使用setpgid 强制它进入孩子的进程组,因为它的 SID 与我孩子的 SID 不同。

我相信我也是

必须说服 Popen 不要创建新会话,或者 使用一个比subprocess 魔法更少的模块,但它仍然允许我读取标准输出和标准错误。

但我也不知道该怎么做。诚然,我对进程组的了解有些有限。

/home/martin >python --version
Python 2.7.1

10 月 12 日更新

我发现新会话不是由 python 创建的,而是由jackd 本身创建的。当我用xterm 替换jackd 时,一切都很好。

  PID  PPID  PGID   SID COMMAND
12239  4024 12239  4024 python (parent, main)
12244 12239 12244 12244 python (forked child after setsid - ok)
12249 12244 12244 12244 xterm  (grandchild has same PGID and SID - ok)

我还找到了this

这些杰克日子调用 setsid() 将自己建立为一个新的 流程组长

只剩下选择

main 知道 jack 的 PID 并明确杀死它,或者 让垂死的孩子杀死杰克德

对此有何建议?

.

【问题讨论】:

这个答案有帮助吗? ***.com/a/4791612 这就是我正在做的事情。但是,我发现问题实际上是jackd本身而不是python。我会相应地更新我的问题。 为什么不只用一个线程,直接控制jack呢?为什么要创建一个单独的子进程来控制实际命令?让它变得如此复杂有什么好处? fork child 用于捕获jackd的输出。我不认为我可以在不阻塞的情况下在***流程中做到这一点。 你应该可以,只要不在主线程中。主线程应该继续做它的 UI 工作,包括。响应来自操作系统的检查。这是此类应用程序中的常见模式。 【参考方案1】:

由于您的孙子设置了自己的会话和 sid,因此您不能期望 group-kill 正常工作。您有三个选择:

    遍历整个子进程树,并从父进程开始将它们全部杀死(这样它们就不会重新启动子进程)。丑陋且过于复杂的解决方案。

    在主(根)进程中使用线程直接控制jack 子进程。这里可能不需要中间子进程,因为它没有任何好处。这是可能的最简单的解决方案。

    如果还得有中间子进程,唯一的办法就是捕获发送给子进程的终止信号,并将它们传送给孙子进程:

child.py:

import signal
import subprocess

# The signal handler for the child process, which passes it further to the grandchild.
def handler(signum, frame):
    print('Killing self, killing the children')

    os.kill(proc.pid, signal.SIGTERM)

    # wait for the process to die gracefully for N secs.
    time.sleep(5)

    os.kill(proc.pid, signal.SIGKILL)

cmd = ["jackd","-R", "-P80", "-t5000", "-dfirewire", "-r44100", "-p256", "-n3"]
po = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

signal.signal(signal.SIGTERM, handler)

for line in po.stdout:
    print(line.rstrip())

请注意,信号处理程序是在进程通信开始之前安装的(行读取)。

只要您的根进程“优雅地”终止子进程(例如,使用 SIGTERM),信号就会传递给孙子进程,它也会被终止/杀死。您甚至可以扩展杀死和等待以及强制杀死它的逻辑。

但是,请记住,如果子进程被 SIGKILL 杀死,将没有机会捕获此信号,并且孙子进程将继续运行孤儿。因为 SIGKILL 在设计上是无法捕获的。

【讨论】:

以上是关于如何在python中杀死一个分叉的孩子及其jackd子进程的主要内容,如果未能解决你的问题,请参考以下文章

Python:在分叉的孩子和父母之间共享变量

在一个父母中分叉多个孩子

检查一个分叉的孩子是不是已经在 perl 中执行

python:杀死孩子或报告他们成功的简单方法?

当我从 c 中的一个分叉的孩子执行()时会发生啥

从另一个环境调用 Perl 在后台