首页 > 解决方案 > 当子进程被发送信号时,父进程在随后的 read() 上收到 KeyboardInterrupt

问题描述

在 MacOS 上使用 Python 3.7:

我有一个多处理池,其中子进程处理工作单元。

反过来,这些 Pool 子进程中的每一个都必须运行一个命令子进程,这是使用 Popen 完成的;例如:

    with Popen([CMD, ARGS], stdin=PIPE, stdout=PIPE, stderr=STDOUT, encoding='utf8', close_fds=True, cwd=JSBOT_DIR, start_new_session=True, ) as process:
        try:
            while process.poll() is None:
                (ready, _, error) = select([process_stdout], [], [process_stdout], timeout)
                if process_stdout in ready:
                    line = process_stdout.readline()
                    while line:
                        LOGGER.info(f'{line.rstrip()}')
                        line = process_stdout.readline()
        except KeyboardInterrupt:
            LOGGER.info('Got KeyboardInterrupt from subprocess')
        # ...
        process.wait(10)

这个命令子进程可以通过发送一个 SIGUSR2 来通知它是时候结束和退出了。

一旦这个命令子过程完成,就会发生一些后处理。这是subprocess.run在 Pool child 中使用实现的,例如:

            result = subprocess.run(['ffmpeg', '-y', '-i', pathname, '-ac', '1', '-ar', '48000', '-c:a', 'pcm_s16le',
                                     normalized_filename], stderr=STDOUT, stdout=PIPE)

我遇到的问题是,在执行 后subprocess.run(),Python 立即发出一个 KeyboardInterrupt,并且Pool孩子立即死亡:

  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 474, in run
    stdout, stderr = process.communicate(input, timeout=timeout)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 926, in communicate
    stdout = self.stdout.read()
KeyboardInterrupt

我尝试设置 SIGUSR2 和 SIGINT 以确保在子初始化期间在池子进程中被忽略;但是,这并没有帮助。我也尝试过使用和不使用 start_new_session=True。我还尝试将 SIGINT 设置为在 Pool 子项中处理,但发生这种情况时不会调用处理程序。如果我故意向 Pool 子项发送 SIGINT,则会调用处理程序。

如果我time.sleep(10)在尝试创建下一个进程之前创建该进程,它将完全休眠,然后立即相信 KeyboardInterrupt 在第一次读取 inside 时已经到达subprocess.run

这种行为似乎是这样的,即向 Pool 子进程的命令子进程传递信号会导致 Python 相信 KeyboardInterrupt 已发送到 Pool 子进程,并且在第一次 IO 尝试时处理此 KeyboardInterrupt。

我的问题是:为什么会发生这种情况(即,我错过了什么?),以及如何防止这种情况发生?

我还注意到,当 Popened 命令终止时,我在命令子进程上下文中捕获并记录了一个 KeyboardInterrupt。这可能是解释器“记住”有一个待处理的 KeyboardInterrupt 最终在稍后处理的情况吗?如果是这样,我该如何抑制它或“清除”键盘中断?

编辑:当我继续玩这个时,我发现如果我注释掉所有的 select/readline/log 循环,一切正常。因此,这似乎是从命令子进程中读取某些内容的行为,这会杀死 Pool 子进程。

标签: pythonpython-3.xpython-multiprocessing

解决方案


推荐阅读