Python防止子线程受到SIGINT符号的影响

2024-04-19 18:47:41 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

signal.signal(signal.SIGINT, handler)

我最初认为,一旦收到一个SIGINT,那么它只会影响我的主线程,然后我就可以管理子线程来终止,。在

然而,我实际观察到的是,按下control+c也会影响我的子线程(我看到,一旦我按下control+c,子线程中的子进程将引发RC 512异常)。在

有人能告诉我们是否只有主线程可以检测到这个信号而不影响子线程吗?在


Tags: 程序应用程序处理程序signal定义信号进程线程
1条回答
网友
1楼 · 发布于 2024-04-19 18:47:41

如果使用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(),则只有子进程将退出。子进程中的信号处理程序无法更改父进程中的任何变量。)

为了避免这种情况,父进程可以在子进程创建期间临时切换到另一个信号处理程序,该处理器只记住是否捕获到信号:

^{pr2}$

使用上述帮助器,其思想是在创建子进程(使用子进程模块或某些操作系统模块函数)之前,调用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)。在


python3.3及更高版本的非Windows操作系统公开了信号掩码,可用于在一段时间内“阻止”信号。阻塞是指信号的传输被推迟,直到被解除阻塞为止;不被忽略。这正是上述信号分流代码试图实现的。在

这些信号没有排队,因此,如果一个信号已经挂起,则忽略相同类型的任何其他信号。(因此,每种类型只能有一个信号,比如SIGINT,可以同时挂起。)

这允许一个模式使用两个helper函数

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})(它在传递之前阻塞),或者{}使用,如果有一个信号挂起,它将立即返回,否则使用None。在


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

然而,在Unix/POSIXy机器上,我们可以阻止SIGINT被发送到子进程,方法是将子进程从控制终端分离出来,并在它自己的会话中运行它。在

subprocess.Popen()中需要两组更改:

  • 执行setsid下的命令或二进制文件:[ "setsid", "program", args.. ]或{},具体取决于您是以列表还是字符串的形式提供要执行的二进制文件。在

    ^{}是一个命令行实用程序,它在h新会话中指定的参数。新会话没有控制终端,这意味着如果用户按下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信号。在

相关问题 更多 >