Python 2.6.x theading / Signals / atexit在某些版本上失败?
-
02-10-2019 - |
题
我看过很多与此有关的问题...但是我的代码 作品 在Python 2.6.2和 失败 在Python 2.6.5上工作。我认为在程序被信号杀死时,通过此模块注册的整个功能不应该在这里计算,因为我抓住信号然后干净地退出,我不应该调用“通过此模块注册的功能”是错误的吗?这里发生了什么?什么是正确的方法?
import atexit, sys, signal, time, threading
terminate = False
threads = []
def test_loop():
while True:
if terminate:
print('stopping thread')
break
else:
print('looping')
time.sleep(1)
@atexit.register
def shutdown():
global terminate
print('shutdown detected')
terminate = True
for thread in threads:
thread.join()
def close_handler(signum, frame):
print('caught signal')
sys.exit(0)
def run():
global threads
thread = threading.Thread(target=test_loop)
thread.start()
threads.append(thread)
while True:
time.sleep(2)
print('main')
signal.signal(signal.SIGINT, close_handler)
if __name__ == "__main__":
run()
Python 2.6.2:
$ python halp.py
looping
looping
looping
main
looping
main
looping
looping
looping
main
looping
^Ccaught signal
shutdown detected
stopping thread
Python 2.6.5:
$ python halp.py
looping
looping
looping
main
looping
looping
main
looping
looping
main
^Ccaught signal
looping
looping
looping
looping
...
looping
looping
Killed <- kill -9 process at this point
2.6.5上的主线程似乎永远不会执行ATEXIT函数。
解决方案
此处的根差实际上与信号和atexit无关,而是改变的行为 sys.exit
.
在2.6.5左右之前 sys.exit
(更准确地说,最高级别的SystemExit将导致口译员退出;如果线程仍在运行,则与POSIX线程一样,它们将被终止。
大约2.6.5,行为改变了: sys.exit
现在与从程序的主要功能返回基本相同。当你这样做的时候 那- 在两个版本中 - 解释器在退出之前等待所有线程。
相关的更改是 Py_Finalize
现在打电话 wait_for_thread_shutdown()
在顶部附近,没有以前。
这种行为变化似乎不正确,主要是因为它不再如记录的那样发挥作用,这简直就是:“退出python”。实际效果不再是从Python退出,而是仅仅是为了退出线程。 (作为旁注, sys.exit
从另一个线程打电话时,从未退出python,但是与所记载的行为的那种晦涩的分歧并没有证明更大的差异。)
我可以看到新行为的吸引力:而不是退出主线程的两种方法(“退出并等待线程”和“立即退出”),只有一种,因为sys.exit基本上与简单地从返回顶部功能。但是,这是一个破裂的变化,与所记载的行为分歧,这远远超过了这一点。
由于这种变化,之后 sys.exit
从上面的信号处理程序中,解释器坐在等待线程退出然后运行 atexit
处理人员之后。由于处理程序本身告诉线程退出,因此结果是僵局。
其他提示
退出 到期的 信号与退出 之内 信号处理程序。捕获信号并使用sys.exit退出是一个干净的出口,而不是由于信号处理程序而导致的出口。因此,是的,我同意它应该在这里运行至少在这里 - 原则上。
但是,信号处理程序有些棘手:它们完全不同步。他们可以随时在任何VM OPODE之间中断程序流。以此代码为例。 (将其视为与您上面的代码相同的形式;我为简洁省略了代码。)
import threading
lock = threading.Lock()
def test_loop():
while not terminate:
print('looping')
with lock:
print "Executing synchronized operation"
time.sleep(1)
print('stopping thread')
def run():
while True:
time.sleep(2)
with lock:
print "Executing another synchronized operation"
print('main')
这里有一个严重的问题:在Run()持有时,可能会收到信号(例如 ^c) lock
. 。如果发生这种情况,您的信号处理程序将运行,锁定仍保持锁定。然后,它将等待test_loop退出,如果该线程正在等待锁,您将陷入僵局。
这是整个问题的类别,这就是为什么 很多 API说不要从信号处理程序内打电话给它们。相反,您应该设置一个标志来告诉主线程在适当的时间关闭。
do_shutdown = False
def close_handler(signum, frame):
global do_shutdown
do_shutdown = True
print('caught signal')
def run():
while not do_shutdown:
...
我的偏爱是避免完全使用sys.t.exit退出程序,并在主要出口点(例如Run()结束())明确进行清理,但是如果需要,您可以在此处使用Atexit。
我不确定这是否完全改变了,但这就是我在2.6.5中完成我的遇到的方式
atexit.register(goodbye)
def goodbye():
print "\nStopping..."