Python 2.6.x العيار / الإشارات / atexit فشل في بعض الإصدارات؟

StackOverflow https://stackoverflow.com/questions/3713360

سؤال

لقد رأيت الكثير من الأسئلة المتعلقة بهذا ... لكن الكود الخاص بي يعمل على بيثون 2.6.2 و فشل للعمل على بيثون 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 أبدًا.

هل كانت مفيدة؟

المحلول

الفرق الجذري هنا لا علاقة له في الواقع بكل من الإشارات و atexit ، بل تغيير في سلوك sys.exit.

قبل حوالي 2.6.5 ، sys.exit (بشكل أكثر دقة ، من شأن SystemExit القبض على المستوى الأعلى) أن يتسبب في خروج المترجم ؛ إذا كانت المواضيع لا تزال قيد التشغيل ، فسيتم إنهاءها ، تمامًا كما هو الحال مع مؤشرات الترابط Posix.

حوالي 2.6.5 ، تغير السلوك: تأثير sys.exit هو الآن هو نفسه العودة من الوظيفة الرئيسية للبرنامج. عندما تفعل الذي - التي-في كلا الإصدارين-ينتظر المترجم المترجم أن يتم ربط جميع المواضيع قبل الخروج.

التغيير ذي الصلة هو ذلك Py_Finalize الآن مكالمات wait_for_thread_shutdown() بالقرب من الأعلى ، حيث لم يكن من قبل.

يبدو هذا التغيير السلوكي غير صحيح ، لأنه لم يعد يعمل كما هو موثق ، وهو ببساطة: "الخروج من Python". لم يعد التأثير العملي للخروج من بيثون ، ولكن ببساطة للخروج من الخيط. (كملاحظة جانبية، sys.exit لم يخرج من Python عندما تم استدعاؤه من موضوع آخر ، لكن هذا الاختلاف الغامض من السلوك الموثق لا يبرر واحدة أكبر بكثير.)

أستطيع أن أرى جاذبية السلوك الجديد: بدلاً من طريقتان للخروج من الخيط الرئيسي ("الخروج وانتظر الخيوط" و "الخروج على الفور") ، هناك واحدة فقط ، لأن Sys.exit مطابق بشكل أساسي للعودة ببساطة من وظيفة أعلى. ومع ذلك ، فهو تغيّر خرق وتباعد السلوك الموثق ، الذي يفوق بكثير ذلك.

بسبب هذا التغيير ، بعد 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')

هناك مشكلة خطيرة هنا: يمكن استلام إشارة (على سبيل المثال. ^C) أثناء تشغيل Run () lock. إذا حدث ذلك ، فسيتم تشغيل معالج الإشارة الخاص بك مع عدم وجود قفل. سوف ينتظر بعد ذلك حتى يخرج Test_Loop ، وإذا كان هذا الموضوع ينتظر القفل ، فسوف تقوم بالدوقة.

هذه فئة كاملة من المشاكل ، وهذا هو السبب في كثير من واجهات برمجة التطبيقات قل عدم الاتصال بهم من داخل معالجات الإشارات. بدلاً من ذلك ، يجب عليك تعيين علامة لإخبار الموضوع الرئيسي لإغلاقه في وقت مناسب.

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 هنا إذا أردت.

لست متأكدًا مما إذا كان هذا قد تم تغييره تمامًا ، لكن هذا هو ما قمت به في 2.6.5


atexit.register(goodbye)

def goodbye():
    print "\nStopping..."
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top