5

我已经看到了很多与此相关的问题......但我的代码适用于 python 2.6.2 并且无法在 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()

蟒蛇2.6.2:

$ python halp.py 
looping
looping
looping
main
looping
main
looping
looping
looping
main
looping
^Ccaught signal
shutdown detected
stopping thread

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

4

3 回答 3

8

这里的根本区别实际上与信号和 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在它们退出后运行处理程序。由于是处理程序本身告诉线程退出,因此结果是死锁。

于 2010-09-21T23:13:50.107 回答
3

由于信号而退出与从信号处理程序中退出不同。捕获信号并使用 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() 持有时,可能会收到一个信号(例如 ^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.exit 完全退出程序,并在主退出点(例如 run() 的结尾)显式地进行清理,但如果需要,您可以在此处使用 atexit。

于 2010-09-14T23:40:41.247 回答
0

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


atexit.register(goodbye)

def goodbye():
    print "\nStopping..."
于 2010-09-14T23:19:37.447 回答