gpt4 book ai didi

python - Python-防止子线程受到SIGINT信号的影响

转载 作者:行者123 更新时间:2023-12-01 08:56:04 24 4
gpt4 key购买 nike

我有一个由运行程序(这是主线程)组成的程序,该程序创建1个或多个子线程,这些子线程主要使用子进程来触发第三方应用程序。

我希望能够在收到SIGINT后优雅地终止所有线程,因此我在主线程中定义了一个处理程序,如下所示:

signal.signal(signal.SIGINT, handler)

尽管最初我收到了SIGINT,但它只会影响我的主线程,然后我将能够管理子线程终止。

但是,我实际上观察到的是,按Control + c也会影响我的子线程(我看到,一旦按Control + c,子线程中的子进程就会引发RC 512的异常)。

有人可以建议是否只有主线程会检测到该信号而不影响子线程吗?

最佳答案

如果使用subprocess.Popen()创建子进程,并且不希望它们被SIGINT信号杀死,请在执行新的二进制文件之前,使用preexec_fn参数将SIGINT信号处置设置为忽略:

child = subprocess.Popen(...,
preexec_fn = lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))

其中 ...是您当前参数的占位符。

如果使用实际线程(线程或线程模块),则Python的信号模块会设置所有内容,以便只有主线程/初始线程才能接收信号或设置信号处理程序。因此,Python中的信号不会真正影响适当的线程。

subprocess.Popen()情况下,子进程最初继承该进程的副本,包括信号处理程序。这意味着存在一个小窗口,子进程可以使用与父进程相同的代码来捕获信号。但是,由于它是一个独立的过程,因此仅可见其副作用。 (例如,如果信号处理程序调用 sys.exit(),则仅子进程将退出。子进程中的信号处理程序无法更改父进程中的任何变量。)

为了避免这种情况,父进程可以临时切换到其他信号处理程序,该信号处理程序仅在子进程创建期间记住是否捕获到信号:
import signal

# Global variables for sigint diversion
sigint_diverted = False # True if caught while diverted
sigint_original = None # Original signal handler

def sigint_divert_handler():
global sigint_diverted
sigint_diverted = True

def sigint_divert(interrupts=False):
"""Temporarily postpone SIGINT signal delivery."""
global sigint_diverted
global sigint_original
sigint_diverted = False
sigint_original = signal.signal(signal.SIGINT, sigint_divert_handler)
signal.siginterrupt(signal.SIGINT, interrupts)

def sigint_restore(interrupts=True):
"""Restore SIGINT signal delivery to original handler."""
global sigint_diverted
global sigint_original
original = sigint_original
sigint_original = None
if original is not None:
signal.signal(signal.SIGINT, original)
signal.siginterrupt(signal.SIGINT, interrupts)
diverted = sigint_diverted
sigint_diverted = False
if diverted and original is not None:
original(signal.SIGINT)

使用上述助手,其想法是在创建子进程(使用子进程模块或某些os模块函数)之前,请调用 sigint_divert()。子进程继承了转移的SIGINT处理程序的副本。创建子进程后,可以通过调用 sigint_restore()还原SIGINT处理。 (请注意,如果您在设置原始SIGINT处理程序后调用了 signal.siginterrupt(signal.SIGINT, False),以便其传递不会引发IOError异常,则应在此处调用 sigint_restore(False)。)

这样,子进程中的信号处理程序就是转向信号处理程序,该信号处理程序仅设置全局标志,而没有执行其他任何操作。当然,您仍然想将 preexec_fn =参数用于 subprocess.Popen(),以便在子进程中执行实际的二进制文件时完全忽略SIGINT信号。
sigint_restore()不仅恢复原始信号处理程序,而且如果转向的信号处理程序捕获到SIGINT信号,则可以通过直接调用原始信号处理程序来“重新引发”该信号。假设原始处理程序是您已经安装的处理程序;否则,您可以改用 os.kill(os.getpid(), signal.SIGKILL)

非Windows操作系统上的Python 3.3和更高版本公开了信号掩码,该掩码可用于在一段时间内“阻止”信号。阻塞意味着信号的传递被推迟,直到被解除阻塞为止。不容忽视。这正是上述信号转移代码试图实现的目标。

信号不会排队,因此,如果一个信号已经挂起,则将忽略任何其他相同类型的信号。 (因此,每种类型的信号(例如SIGINT)只能同时处于待处理状态。)

这允许使用两个辅助功能的模式,
def block_signals(sigset = { signal.SIGINT }):
mask = signal.pthread_sigmask(signal.SIG_BLOCK, {})
signal.pthread_sigmask(signal.SIG_BLOCK, sigset)
return mask

def restore_signals(mask):
signal.pthread_sigmask(signal.SIG_SETMASK, mask)

这样一来,在创建线程或子进程之前,请先调用 mask = block_signals(),然后再创建 restore_signals(mask)。在创建的线程或子进程中,默认情况下将阻止SIGINT信号。

阻塞的SIGINT信号也可以使用 signal.sigwait({signal.SIGINT})(阻塞直到发送一个信号)来使用,或者使用 signal.sigtimedwait({signal.SIGINT}, 0)(如果有一个未决,它随信号立即返回)使用,否则使用 None

当子进程管理自己的信号掩码和信号处理程序时,我们不能使它忽略SIGINT信号。

在Unix/POSIXy机器上,我们可以通过将子进程与控制终端分离并在其自己的 session 中运行,来阻止将SIGINT发送到子进程。
subprocess.Popen()需要进行两组更改:
  • setsid下执行命令或二进制文件:[ "setsid", "program", args.. ]"setsid sh -c 'command'",取决于您提供的二进制文件是要以列表形式还是字符串形式执行。

    setsid 是一个命令行实用程序,可在新 session 中运行带有指定参数的指定程序。新 session 没有控制终端,这意味着如果用户按下Ctrl + C,它将不会收到SIGINT。
  • 如果父级不将管道用于子进程的stdinstdoutstderr,则应将它们显式打开为os.devnull:
    stdin=open(os.devnull, 'rb')stdout=open(os.devnull, 'wb')stderr=open(os.devnull, 'wb')
    这确保了子过程不会退回到控制终端之下。 (当用户按下Ctrl + C时,它是向每个进程发送SIGINT信号的控制终端。)

  • 如果父进程愿意,则可以使用 os.kill(child.pid, signal.SIGINT)向子进程发送SIGINT信号。

    关于python - Python-防止子线程受到SIGINT信号的影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52763508/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com