python 2.6.x theading / Signals /atexit 在某些版本上失败?

发布于 2024-09-19 06:18:30 字数 1446 浏览 10 评论 0原文

我已经看到很多与此相关的问题...但是我的代码在 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()

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

I've seen a lot of questions related to this... but my code works on python 2.6.2 and fails to work on python 2.6.5. Am I wrong in thinking that the whole atexit "functions registered via this module are not called when the program is killed by a signal" thing shouldn't count here because I'm catching the signal and then exiting cleanly? What's going on here? Whats the proper way to do this?

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

The main thread on 2.6.5 appears to never execute the atexit functions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

你另情深 2024-09-26 06:18:30

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

The root difference here is actually unrelated to both signals and atexit, but rather a change in the behavior of sys.exit.

Before around 2.6.5, sys.exit (more accurately, SystemExit being caught at the top level) would cause the interpreter to exit; if threads were still running, they'd be terminated, just as with POSIX threads.

Around 2.6.5, the behavior changed: the effect of sys.exit is now essentially the same as returning from the main function of the program. When you do that--in both versions--the interpreter waits for all threads to be joined before exiting.

The relevant change is that Py_Finalize now calls wait_for_thread_shutdown() near the top, where it didn't before.

This behavioral change seems incorrect, primarily because it no longer functions as documented, which is simply: "Exit from Python." The practical effect is no longer to exit from Python, but simply to exit the thread. (As a side note, sys.exit has never exited Python when called from another thread, but that obscure divergance from documented behavior doesn't justify a much bigger one.)

I can see the appeal of the new behavior: rather than two ways to exit the main thread ("exit and wait for threads" and "exit immediately"), there's only one, as sys.exit is essentially identical to simply returning from the top function. However, it's a breaking change and diverges from documented behavior, which far outweighs that.

Because of this change, after sys.exit from the signal handler above, the interpreter sits around waiting for threads to exit and then runs atexit handlers after they do. Since it's the handler itself that tells the threads to exit, the result is a deadlock.

故人爱我别走 2024-09-26 06:18:30

由于信号而退出与从信号处理程序内退出不同。捕获信号并使用 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。

Exiting due to a signal is not the same as exiting from within a signal handler. Catching a signal and exiting with sys.exit is a clean exit, not an exit due to a signal handler. So, yes, I agree that it should run atexit handlers here--at least in principle.

However, there's something tricky about signal handlers: they're completely asynchronous. They can interrupt the program flow at any time, between any VM opcode. Take this code, for example. (Treat this as the same form as your code above; I've omitted code for brevity.)

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')

There's a serious problem here: a signal (eg. ^C) may be received while run() is holding lock. If that happens, your signal handler will be run with the lock still held. It'll then wait for test_loop to exit, and if that thread is waiting for the lock, you'll deadlock.

This is a whole category of problems, and it's why a lot of APIs say not to call them from within signal handlers. Instead, you should set a flag to tell the main thread to shut down at an appropriate time.

do_shutdown = False
def close_handler(signum, frame):
    global do_shutdown
    do_shutdown = True
    print('caught signal')

def run():
    while not do_shutdown:
        ...

My preference is to avoid exiting the program with sys.exit entirely and to explicitly do cleanup at the main exit point (eg. the end of run()), but you can use atexit here if you want.

穿透光 2024-09-26 06:18:30

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


atexit.register(goodbye)

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

I'm not sure if this was entirely changed, but this is how I have my atexit done in 2.6.5


atexit.register(goodbye)

def goodbye():
    print "\nStopping..."
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文