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、プログラムのメイン関数から戻るのと本質的に同じになりました。これを行う(両方のバージョンで)、インタープリターはすべてのスレッドが結合されるのを待ってから終了します。

関連する変更は、以前はなかった上部近くのPy_Finalize呼び出しです。wait_for_thread_shutdown()

この動作の変更は、主に文書化されたとおりに機能しなくなったため、正しくないように見えます。これは、単に「Pythonから終了」です。実際の効果は、Pythonを終了することではなく、単にスレッドを終了することです。(補足として、sys.exit別のスレッドから呼び出されたときにPythonを終了したことはありませんが、文書化された動作からのあいまいな相違は、はるかに大きなものを正当化するものではありません。)

新しい動作の魅力を見ることができます。メインスレッドを終了する2つの方法(「終了してスレッドを待つ」と「すぐに終了する」)ではなく、sys.exitは基本的に、単に戻るのと同じであるため、1つしかありません。トップ機能。ただし、これは重大な変更であり、文書化された動作とは異なり、それをはるかに上回ります。

この変更により、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 がシグナル ハンドラー内から 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 に答える