в некоторых версиях python 2.6.x theading / сигналов / atexit не удается? - PullRequest
5 голосов
/ 15 сентября 2010

Я видел много вопросов, связанных с этим ... но мой код работает на 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.

Ответы [ 3 ]

8 голосов
/ 22 сентября 2010

Корневая разница здесь на самом деле не связана ни с сигналами, ни с atexit, а скорее с изменением поведения sys.exit.

До примерно 2.6.5, sys.exit (точнее, перехват SystemExitна верхнем уровне) вызовет выход интерпретатора;если бы потоки все еще работали, они были бы прерваны, как с потоками POSIX.

Вокруг 2.6.5, поведение изменилось: эффект sys.exit теперь по сути такой же, как и возвращение из основной функции.программы.Когда вы делаете , что - в обеих версиях - интерпретатор ожидает соединения всех потоков перед выходом.

Соответствующее изменение заключается в том, что Py_Finalize теперь вызывает wait_for_thread_shutdown() возлеtop, там, где это раньше не было.

Это изменение поведения кажется неправильным, в первую очередь потому, что оно больше не работает так, как задокументировано, а именно: «Выход из Python».Практический эффект - больше не выход из Python, а просто выход из потока.(Как примечание: sys.exit никогда не выходил из Python при вызове из другого потока, но это неясное расхождение с документированным поведением не оправдывает намного большее.)

Я вижу привлекательность новогоповедение: вместо двух способов выхода из основного потока («выход и ожидание потоков» и «немедленный выход»), есть только один, так как sys.exit по сути идентичен простому возврату из верхней функции.Однако это серьезное изменение, которое отличается от задокументированного поведения, которое намного перевешивает это.

Из-за этого изменения после sys.exit из обработчика сигнала, приведенного выше, интерпретатор бездействует, ожидая выхода потоков, и затем запускаетсяatexit обработчики после того, как они это делают.Поскольку сам обработчик сообщает потокам о выходе, результатом является тупик.

3 голосов
/ 15 сентября 2010

Выход из-за сигнала не совпадает с выходом из в обработчике сигнала. Перехват сигнала и выход с помощью 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')

Здесь есть серьезная проблема: сигнал (например, ^ C) может быть получен, пока run () удерживает lock. Если это произойдет, ваш обработчик сигнала будет запущен с заблокированной блокировкой. Затем он будет ожидать выхода test_loop, и если этот поток ожидает блокировки, вы будете в тупике.

Это целая категория проблем, и поэтому API lot говорят, что не следует вызывать их из обработчиков сигналов. Вместо этого вы должны установить флаг, чтобы сообщить основному потоку о необходимости завершить работу в соответствующее время.

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, если хотите.

0 голосов
/ 15 сентября 2010

Я не уверен, что это было полностью изменено, но так я сделал мой atexit в 2.6.5


atexit.register(goodbye)

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