2010-09-14 2 views
5

Я видел много вопросов, связанных с этим ... но мой код работает на python 2.6.2 и сбой для работы на python 2.6.5. Я ошибаюсь, думая, что все функции atexit, зарегистрированные через этот модуль, не вызывают, когда программа убита сигналом, «вещь не должна учитываться здесь, потому что я поймаю сигнал, а затем выхожу чисто? Что тут происходит? Каков правильный способ сделать это?python 2.6.x theading/сигналы/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.

+0

Я пробовал код на Python 2.6.5 и Python 2.6.1 на OS X 10.6, и они ведут себя так, как описано в вопросе (2.6.5, не выполняющий atexit, а 2.6.1). Надеюсь, что люди, более разбирающиеся в исходном коде Python, будут советоваться о том, что изменилось. –

+0

, возможно, потерял интерес к этому или нашел обходное решение, но меня все еще интересует то, что изменилось между двумя версиями Python, чтобы вызвать это. Вместо того, чтобы снова задавать один и тот же вопрос, я собираюсь начать щедрость по этому поводу. Надеюсь, он не против. –

ответ

7

Коренное отличие здесь фактически не связано с обоими сигналами и 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 после того, как они это делают. Поскольку сам обработчик сообщает, что потоки завершаются, результат является тупиком.

+1

Большое спасибо, Гленн. Теперь, когда я знаю, что искать, я нахожу соответствующий отчет о проблеме Python [здесь] (http://bugs.python.org/issue1722344). Я согласен, что это большое изменение, которое должно было быть сделано в более чем незначительном выпуске. –

0

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


atexit.register(goodbye) 

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

с 2.6 atexit.register можно использовать в качестве декоратора. – lostincode

+0

Хм, ну это странно. Вы уверены, что используете тот же код, и он не кэшируется где-то еще или что-то странное? – Falmarri

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

Там серьезная проблема: сигнал может быть получен во время запуска() есть (например,^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 полностью и явно делать уборку в главной точке выхода (например, в конце пробега()), но вы можете использовать atexit здесь, если вы хотите ,

 Смежные вопросы

  • Нет связанных вопросов^_^