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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…