多线程Python中的信号处理
这应该是非常简单的,我很惊讶,我还没有找到这个问题的答案已经在stackoverflow 我有一个类似守护进程的程序,它需要响应SIGTERM和SIGINT信号,以便与upstart很好地协作。我读到这样做的最佳方法是在与主线程分离的线程中运行程序的主循环,并让主线程处理信号。然后,当接收到信号时,信号处理程序应该通过设置一个在主循环中例行检查的哨兵标志来通知主循环退出 我试过这样做,但没有达到我预期的效果。请参阅下面的代码:多线程Python中的信号处理,python,linux,multithreading,Python,Linux,Multithreading,这应该是非常简单的,我很惊讶,我还没有找到这个问题的答案已经在stackoverflow 我有一个类似守护进程的程序,它需要响应SIGTERM和SIGINT信号,以便与upstart很好地协作。我读到这样做的最佳方法是在与主线程分离的线程中运行程序的主循环,并让主线程处理信号。然后,当接收到信号时,信号处理程序应该通过设置一个在主循环中例行检查的哨兵标志来通知主循环退出 我试过这样做,但没有达到我预期的效果。请参阅下面的代码: from threading import Thread impor
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()
我通过以下两种方式对此进行了测试:
(一)
(二)
在这两种情况下,我都希望将其写入输出:
run started
handling signal: 15
run exited
join completed
在第一种情况下,程序退出,但我看到的是:
run started
在第二种情况下,当按下ctrl+c且程序不退出时,SIGTERM信号似乎被忽略
我在这里遗漏了什么?问题在于,如中所述: Python信号处理程序不会在低级(C)信号处理程序中执行。相反,低级信号处理程序设置一个标志,告诉虚拟机在稍后的时间点(例如在下一个字节码指令)执行相应的Python信号处理程序 纯用C语言实现的长时间运行的计算(例如在大量文本上进行正则表达式匹配)可能会在任意时间内不间断地运行,而与接收到的任何信号无关。计算完成后将调用Python信号处理程序 您的主线程在
threading.thread.join
上被阻塞,这最终意味着它在调用pthread\u join
时在C中被阻塞。当然,这不是一个“长时间运行的计算”,它是系统调用上的一个块……但是,在该调用完成之前,您的信号处理程序无法运行
而且,在某些平台上,pthread\u join
会在信号上出现EINTR
故障,而在其他平台上则不会。在linux上,我相信这取决于您是选择BSD样式还是默认的siginterrupt
行为,但默认值是否
那么,你能做些什么呢 好吧,我很确定,它实际上改变了Linux上的默认行为,因此,如果您升级,您将不需要做任何事情;只需在3.3+下运行,您的代码就会按预期工作。至少在OS X上的CPython 3.4和Linux上的3.3对我来说是这样。(如果我错了,我不确定这是否是CPython中的bug,所以您可能希望在python列表中提出它,而不是打开一个问题…)
另一方面,在3.3之前的版本中,
信号
模块肯定不会公开您自己解决此问题所需的工具。因此,如果无法升级到3.3,解决方案是等待可中断的事件,如条件
或事件
。子线程在退出之前通知事件,主线程在加入子线程之前等待事件。这绝对是令人讨厌的。我找不到任何东西可以保证它会有所不同;这恰好适用于我在OS X上的CPython 2.7和3.2以及Linux上的CPython 2.6和2.7的各种版本中……abarnert的答案是正确的。然而,我仍然在使用Python2.7。为了自己解决这个问题,我编写了一个InterruptableThread类
现在它不允许向线程目标传递额外的参数。Join也不接受超时参数。这只是因为我不需要那样做。如果需要,可以添加它。如果您自己使用,您可能希望删除输出语句。它们只是一种评论和测试的方式
import threading
import signal
import sys
class InvalidOperationException(Exception):
pass
# noinspection PyClassHasNoInit
class GlobalInterruptableThreadHandler:
threads = []
initialized = False
@staticmethod
def initialize():
signal.signal(signal.SIGTERM, GlobalInterruptableThreadHandler.sig_handler)
signal.signal(signal.SIGINT, GlobalInterruptableThreadHandler.sig_handler)
GlobalInterruptableThreadHandler.initialized = True
@staticmethod
def add_thread(thread):
if threading.current_thread().name != 'MainThread':
raise InvalidOperationException("InterruptableThread objects may only be started from the Main thread.")
if not GlobalInterruptableThreadHandler.initialized:
GlobalInterruptableThreadHandler.initialize()
GlobalInterruptableThreadHandler.threads.append(thread)
@staticmethod
def sig_handler(signum, frame):
sys.stdout.write("handling signal: %s\n" % signum)
sys.stdout.flush()
for thread in GlobalInterruptableThreadHandler.threads:
thread.stop()
GlobalInterruptableThreadHandler.threads = []
class InterruptableThread:
def __init__(self, target=None):
self.stop_requested = threading.Event()
self.t = threading.Thread(target=target, args=[self]) if target else threading.Thread(target=self.run)
def run(self):
pass
def start(self):
GlobalInterruptableThreadHandler.add_thread(self)
self.t.start()
def stop(self):
self.stop_requested.set()
def is_stop_requested(self):
return self.stop_requested.is_set()
def join(self):
try:
while self.t.is_alive():
self.t.join(timeout=1)
except (KeyboardInterrupt, SystemExit):
self.stop_requested.set()
self.t.join()
sys.stdout.write("join completed\n")
sys.stdout.flush()
该类可以用两种不同的方式使用。您可以子类InterruptableThread:
import time
import sys
from interruptable_thread import InterruptableThread
class Foo(InterruptableThread):
def __init__(self):
InterruptableThread.__init__(self)
def run(self):
sys.stdout.write("run started\n")
sys.stdout.flush()
while not self.is_stop_requested():
time.sleep(2)
sys.stdout.write("run exited\n")
sys.stdout.flush()
sys.stdout.write("all exited\n")
sys.stdout.flush()
foo = Foo()
foo2 = Foo()
foo.start()
foo2.start()
foo.join()
foo2.join()
或者您也可以像threading.thread那样使用它。不过,run方法必须将InterruptableThread对象作为参数
import time
import sys
from interruptable_thread import InterruptableThread
def run(t):
sys.stdout.write("run started\n")
sys.stdout.flush()
while not t.is_stop_requested():
time.sleep(2)
sys.stdout.write("run exited\n")
sys.stdout.flush()
t1 = InterruptableThread(run)
t2 = InterruptableThread(run)
t1.start()
t2.start()
t1.join()
t2.join()
sys.stdout.write("all exited\n")
sys.stdout.flush()
你想怎么做就怎么做。我在这里也遇到了同样的问题。在阅读了她的答案后,我改为Python 3并解决了这个问题。但我确实喜欢将我的所有程序都更改为python 3。所以,我通过避免在发送信号之前调用thread join()来解决我的程序。下面是我的代码 这不是很好,但用python 2.7解决了我的程序。我的问题被标记为重复,因此我将解决方案放在这里。
import threading, signal, time, os
RUNNING = True
threads = []
def monitoring(tid, itemId=None, threshold=None):
global RUNNING
while(RUNNING):
print "PID=", os.getpid(), ";id=", tid
time.sleep(2)
print "Thread stopped:", tid
def handler(signum, frame):
print "Signal is received:" + str(signum)
global RUNNING
RUNNING=False
#global threads
if __name__ == '__main__':
signal.signal(signal.SIGUSR1, handler)
signal.signal(signal.SIGUSR2, handler)
signal.signal(signal.SIGALRM, handler)
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGQUIT, handler)
print "Starting all threads..."
thread1 = threading.Thread(target=monitoring, args=(1,), kwargs={'itemId':'1', 'threshold':60})
thread1.start()
threads.append(thread1)
thread2 = threading.Thread(target=monitoring, args=(2,), kwargs={'itemId':'2', 'threshold':60})
thread2.start()
threads.append(thread2)
while(RUNNING):
print "Main program is sleeping."
time.sleep(30)
for thread in threads:
thread.join()
print "All threads stopped."
当t.处于活动状态时,尝试用
替换t.join()
:t.join(1)
。你的主线每秒钟都会醒来检查信号。更多阅读:“这绝对是黑客行为”——我一般不会这么说。与简单地使用join
相比,在更高的抽象级别上同步线程是明智的。如果您的目标是等待线程退出(如此特定示例),那么join
是正确的工具;如果要等待工作负载完成,a条件
等更有意义。例如,工作负载可以在一个池线程中执行,该线程毕竟不会立即退出。在Windows上不起作用,并且除SIGINT之外的所有线程在Windows for Python 3.6上都不可用。请欣赏本文,但Python 2.7即将灭亡,因此您可能需要重新考虑针对3.7的解决方案
import time
import sys
from interruptable_thread import InterruptableThread
def run(t):
sys.stdout.write("run started\n")
sys.stdout.flush()
while not t.is_stop_requested():
time.sleep(2)
sys.stdout.write("run exited\n")
sys.stdout.flush()
t1 = InterruptableThread(run)
t2 = InterruptableThread(run)
t1.start()
t2.start()
t1.join()
t2.join()
sys.stdout.write("all exited\n")
sys.stdout.flush()
import threading, signal, time, os
RUNNING = True
threads = []
def monitoring(tid, itemId=None, threshold=None):
global RUNNING
while(RUNNING):
print "PID=", os.getpid(), ";id=", tid
time.sleep(2)
print "Thread stopped:", tid
def handler(signum, frame):
print "Signal is received:" + str(signum)
global RUNNING
RUNNING=False
#global threads
if __name__ == '__main__':
signal.signal(signal.SIGUSR1, handler)
signal.signal(signal.SIGUSR2, handler)
signal.signal(signal.SIGALRM, handler)
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGQUIT, handler)
print "Starting all threads..."
thread1 = threading.Thread(target=monitoring, args=(1,), kwargs={'itemId':'1', 'threshold':60})
thread1.start()
threads.append(thread1)
thread2 = threading.Thread(target=monitoring, args=(2,), kwargs={'itemId':'2', 'threshold':60})
thread2.start()
threads.append(thread2)
while(RUNNING):
print "Main program is sleeping."
time.sleep(30)
for thread in threads:
thread.join()
print "All threads stopped."