Python: 如何防止子进程接收CTRL-C / Control-C / SIGINT

47 投票
5 回答
17188 浏览
提问于 2025-04-16 12:05

我现在正在为一个在命令行中运行的专用服务器开发一个包装器。这个包装器通过子进程启动服务器,并监控和响应它的输出。

这个专用服务器必须明确接收到一个命令才能优雅地关闭。因此,CTRL-C 这个操作不能传递给服务器进程。

如果我捕获 KeyboardInterrupt 异常或者在 Python 中重写 SIGINT 处理程序,服务器进程仍然会接收到 CTRL-C 的信号,并立即停止。

所以我想问的是:如何防止子进程接收到 CTRL-C / Control-C / SIGINT 的信号呢?

5 个回答

10

你可以这样做,让它在Windows和Unix系统上都能运行:

import subprocess
import sys

def pre_exec():
    # To ignore CTRL+C signal in the new process
    signal.signal(signal.SIGINT, signal.SIG_IGN)

if sys.platform.startswith('win'):
    # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
    # If CREATE_NEW_PROCESS_GROUP flag is specified, CTRL+C signals will be disabled
    my_sub_process=subprocess.Popen(["executable"], creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
else:
    my_sub_process=subprocess.Popen(["executable"], preexec_fn=pre_exec)
27

结合其他一些回答,这里有个解决办法——没有信号会被发送到主应用程序并转发给子进程。

在Python 3.11及更新版本中,你可以使用process_group这个参数来创建进程:

from subprocess import Popen

Popen('do_not_want_signals', process_group=0)

而在Python 3.10及更早版本中,你可以使用preexec_fn

import os
from subprocess import Popen

def preexec(): # Don't forward signals.
    os.setpgrp()

Popen('do_not_want_signals', preexec_fn=preexec)
# The above can be shortened to:
Popen('do_not_want_signals', preexec_fn=os.setpgrp)
44

在#python的IRC频道(Freenode)里,有人帮我指出了subprocess.Popen(...)preexec_fn参数:

如果preexec_fn设置为一个可调用的对象,这个对象会在子进程执行之前被调用。(仅限Unix系统)

因此,下面的代码解决了这个问题(仅限Unix系统):

import subprocess
import signal

def preexec_function():
    # Ignore the SIGINT signal by setting the handler to the standard
    # signal handler SIG_IGN.
    signal.signal(signal.SIGINT, signal.SIG_IGN)

my_process = subprocess.Popen(
    ["my_executable"],
    preexec_fn = preexec_function
)

注意:这个信号实际上并没有被阻止到达子进程。相反,上面的preexec_fn会覆盖信号的默认处理方式,让信号被忽略。因此,如果子进程再次覆盖了的处理方式,这个解决方案可能就不管用了。

另一个注意:这个解决方案适用于各种子进程,也就是说,不仅限于用Python写的子进程。例如,我正在为其编写包装器的专用服务器实际上是用Java写的。

撰写回答