一尘不染

多线程Python中的信号处理

linux

这应该非常简单,并且令我感到惊讶的是,我还没找到关于stackoverflow的答案。

我有一个类似程序的守护程序,该程序需要响应SIGTERM和SIGINT信号才能与新贵一起正常工作。我读到最好的方法是在与主线程不同的线程中运行程序的主循环,并让主线程处理信号。然后,当接收到信号时,信号处理程序应通过设置通常在主循环中检查的哨兵标志来告诉主循环退出。

我已经尝试过这样做,但是它没有按我预期的方式工作。请参见下面的代码:

from threading import Thread
import signal
import time
import sys

stop_requested = False

def sig_handler(signum, frame):
    sys.stdout.write("handling signal: %s\n" % signum)
    sys.stdout.flush()

    global stop_requested
    stop_requested = True

def run():
    sys.stdout.write("run started\n")
    sys.stdout.flush()
    while not stop_requested:
        time.sleep(2)

    sys.stdout.write("run exited\n")
    sys.stdout.flush()

signal.signal(signal.SIGTERM, sig_handler)
signal.signal(signal.SIGINT, sig_handler)

t = Thread(target=run)
t.start()
t.join()
sys.stdout.write("join completed\n")
sys.stdout.flush()

我通过以下两种方式对此进行了测试:

1)

$ python main.py > output.txt&
[2] 3204
$ kill -15 3204

2)

$ python main.py
ctrl+c

在两种情况下,我都希望将其写入输出:

run started
handling signal: 15
run exited
join completed

在第一种情况下,程序退出了,但是我看到的是:

run started

在第二种情况下,当按ctrl + c键且程序不退出时,似乎忽略了SIGTERM信号。

我在这里想念什么?


阅读 513

收藏
2020-06-03

共1个答案

一尘不染

问题是,如Python信号处理程序的执行中所述

Python信号处理程序不会在底层(C)信号处理程序中执行。相反,低级信号处理程序设置一个标志,该标志告诉虚拟机在以后的点(例如,在下一个字节码指令处)执行相应的Python信号处理程序。

纯粹以C语言实现的长时间运行的计算(例如,大文本正文上的正则表达式匹配)可能会在任意时间内连续运行,而不管收到任何信号。计算完成后,将调用Python信号处理程序。

您的主线程被阻塞在上threading.Thread.join,这最终意味着它在pthread_join调用中被C阻塞了。当然,这不是“长时间运行的计算”,而是syscall上的一个块……但是,在该调用结束之前,您的信号处理程序将无法运行。

而且,虽然在某些平台上pthread_joinEINTR因信号失败,但在其他平台上却不会。在Linux上,我相信这取决于您选择的是BSD样式还是默认siginterrupt行为,但是默认值为no。


所以你能对它做点啥?

好吧,我很确定Python
3.3中对信号处理
更改实际上改变了Linux上的默认行为,因此,如果升级,您将不需要做任何事情。只需在3.3+下运行,您的代码即可按预期运行。至少它对我来说适用于OS
X上的CPython 3.4和Linux上的3.3。(如果我错了,我不确定这是否是CPython中的错误,因此您可能想在python-
list上引发它,而不是打开一个问题…)

另一方面,在3.3之前的版本中,该signal模块肯定不会提供您自己解决此问题所需的工具。因此,如果您不能升级到3.3,解决方案是等待诸如a
Condition或an之类的可中断内容Event。子线程在退出之前立即通知事件,而主线程在加入子线程之前等待事件。这绝对是hacky。而且我找不到任何可以保证会有所作为的东西。它恰好可以在OS
X的各种CPython 2.7和3.2以及Linux的2.6和2.7的各种版本中为我工作…

2020-06-03