Python 2.6.x Theading / signals / atexitいくつかのバージョンで失敗しますか?
-
02-10-2019 - |
質問
私はこれに関連する多くの質問を見てきました...しかし私のコード 作品 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関数を実行しないように見えます。
解決
ここでのルートの違いは、実際には両方の信号と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を終了したことはありませんが、文書化された動作からのあいまいな発散は、はるかに大きな動作を正当化しません。)
新しい動作の魅力を見ることができます。メインスレッド(「出口とスレッドを待つ」と「すぐに終了する」を終了する2つの方法ではなく、Sys.Exitは本質的に単に戻るのと本質的に同一であるため、1つしかありません。上位関数。しかし、それは壊れた変化であり、文書化された行動とは異なり、それははるかにそれを上回ります。
この変更のために、その後 sys.exit
上記の信号ハンドラーから、通訳者はスレッドが終了してから実行されるのを待つ周りに座っています atexit
彼らがそうした後のハンドラー。スレッドに出口を指示するのはハンドラー自体であるため、結果はデッドロックです。
他のヒント
終了 期限 信号への出口と同じではありません 以内に 信号ハンドラー。信号をキャッチして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を使用できます。
これが完全に変更されたかどうかはわかりませんが、これが2.6.5で私のaTexitを完成させた方法です
atexit.register(goodbye)
def goodbye():
print "\nStopping..."