【问题标题】:python 2.6.x theading / signals /atexit fail on some versions?python 2.6.x theading / 信号 /atexit 在某些版本上失败?
【发布时间】:2011-04-12 10:02:24
【问题描述】:

我已经看到很多与此相关的问题...但是我的代码 works 在 python 2.6.2 上运行,而 fails 在 python 2.6.5 上运行。我认为整个 atexit“当程序被信号杀死时不会调用通过此模块注册的函数”这件事不应该算在内,因为我正在捕捉信号然后干净地退出,我错了吗?这里发生了什么?这样做的正确方法是什么?

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 函数。

【问题讨论】:

  • 我在 OS X 10.6 上尝试了 Python 2.6.5 和 Python 2.6.1 上的代码,它们的行为与问题中描述的一样(2.6.5 不执行 atexit 而 2.6.1 执行) .我希望更精通 Python 源代码的人能就更改的内容提出建议。
  • 可能对此失去了兴趣或找到了解决方法,但我仍然对两个 Python 版本之间触发此问题的变化感兴趣。我不会再问同样的问题,而是要开始赏金。我希望他不介意。

标签: python multithreading signals atexit


【解决方案1】:

这里的根本区别实际上与信号和 atexit 无关,而是sys.exit 行为的变化。

在 2.6.5 左右之前,sys.exit(更准确地说,SystemExit 在顶层被捕获)会导致解释器退出;如果线程仍在运行,它们将被终止,就像 POSIX 线程一样。

在 2.6.5 左右,行为发生了变化:sys.exit 现在的效果与从程序的 main 函数返回基本相同。当您在两个版本中这样做时,解释器会等待所有线程加入后退出。

相关的变化是Py_Finalize 现在在顶部附近调用wait_for_thread_shutdown(),以前没有。

这种行为变化似乎不正确,主要是因为它不再像文档中描述的那样起作用,这很简单:“从 Python 中退出”。实际效果不再是退出Python,而是简单的退出线程。 (作为旁注,sys.exit 在从另一个线程调用时从未退出 Python,但与记录行为的模糊差异并不能证明更大的差异。)

我可以看到新行为的吸引力:不是两种退出主线程的方式(“退出并等待线程”和“立即退出”),只有一种,因为 sys.exit 本质上等同于简单从顶层函数返回。然而,这是一个突破性的变化,与记录在案的行为背道而驰,远远超过了这一点。

由于这一变化,在上述信号处理程序的sys.exit 之后,解释器等待线程退出,然后在它们退出后运行atexit 处理程序。由于是处理程序本身告诉线程退出,因此结果是死锁。

【讨论】:

  • 非常感谢,格伦。现在我知道要查找什么,我找到了相关的 Python 问题报告here。我同意这是一个重大变化,应该在不止一个小版本中完成。
【解决方案2】:

退出 due 到一个信号与退出 within 一个信号处理程序不同。捕获信号并使用 sys.exit 退出是一个干净的退出,而不是由于信号处理程序而导致的退出。所以,是的,我同意它应该在这里运行 atexit 处理程序——至少在原则上是这样。

但是,信号处理程序有一些棘手的问题:它们是完全异步的。它们可以在任何 VM 操作码之间随时中断程序流。以这段代码为例。 (将其视为与您上面的代码相同的形式;为简洁起见,我省略了代码。)

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() 持有 lock 时,可能会收到一个信号(例如 ^C)。如果发生这种情况,您的信号处理程序将在仍持有锁的情况下运行。然后它会等待 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.exit 退出程序,并在主退出点(例如 run() 结束)显式进行清理,但如果需要,您可以在此处使用 atexit。

【讨论】:

    【解决方案3】:

    我不确定这是否完全改变了,但这就是我在 2.6.5 中完成 atexit 的方式

    
    atexit.register(goodbye)
    
    def goodbye():
        print "\nStopping..."
    

    【讨论】:

    • 从 2.6 开始 atexit.register 可以用作装饰器。
    • 嗯,这很奇怪。您确定您正在运行相同的代码,并且它没有缓存在其他地方或类似的奇怪的地方吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多