2010-09-14 23 views
5

Tôi đã nhìn thấy rất nhiều câu hỏi liên quan đến điều này ... nhưng mã của tôi hoạt động trên trăn 2.6.2 và không hoạt động để hoạt động trên trăn 2.6.5. Tôi có sai khi nghĩ rằng toàn bộ các chức năng "không được phép đăng ký qua mô-đun này không được gọi khi chương trình bị giết bởi tín hiệu" điều không nên tính ở đây vì tôi đang bắt tín hiệu và sau đó thoát ra khỏi sạch? Những gì đang xảy ra ở đây? Whats cách thích hợp để làm điều này?python 2.6.x theading/signal/atexit thất bại trên một số phiên bản?

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 

Các chủ đề chính trên 2.6.5 dường như không bao giờ thực hiện các chức năng atexit.

+0

Tôi đã thử mã trên Python 2.6.5 và Python 2.6.1 trên OS X 10.6 và chúng hoạt động giống như được mô tả trong câu hỏi (2.6.5 không thực thi lệnh atexit trong khi 2.6.1 có). Tôi hy vọng mọi người thông thạo hơn trong mã nguồn Python sẽ tư vấn về những gì đã thay đổi. –

+0

có thể đã mất sự quan tâm đến điều này hoặc tìm thấy một giải pháp thay thế, nhưng tôi vẫn quan tâm đến những gì đã thay đổi giữa hai phiên bản Python để kích hoạt điều này. Thay vì hỏi cùng một câu hỏi một lần nữa, tôi sẽ bắt đầu một tiền thưởng về điều này. Tôi hy vọng anh ta không phiền. –

Trả lời

7

Sự khác biệt gốc ở đây thực sự không liên quan đến cả tín hiệu và sự ngoại tuyến, mà là thay đổi về hành vi của sys.exit.

Trước khoảng 2.6.5, sys.exit (chính xác hơn, SystemExit bị bắt ở cấp cao nhất) sẽ khiến trình thông dịch thoát; nếu các chủ đề vẫn đang chạy, chúng sẽ bị chấm dứt, giống như với các chủ đề POSIX.

Khoảng 2.6.5, hành vi đã thay đổi: hiệu ứng của sys.exit hiện nay cơ bản giống như trả về từ chức năng chính của chương trình. Khi bạn thực hiện rằng - trong cả hai phiên bản - trình thông dịch chờ tất cả các chuỗi được nối trước khi thoát.

Thay đổi có liên quan là Py_Finalize hiện gọi số wait_for_thread_shutdown() ở gần đầu, nơi trước đó chưa có.

Thay đổi hành vi này có vẻ không chính xác, chủ yếu là vì nó không còn hoạt động như tài liệu, mà chỉ đơn giản là: "Thoát khỏi Python". Hiệu ứng thực tế không còn để thoát khỏi Python, mà chỉ đơn giản là thoát khỏi luồng. (Là một lưu ý phụ, sys.exit chưa bao giờ thoát khỏi Python khi được gọi từ một chuỗi khác, nhưng sự khác biệt mơ hồ đó khỏi hành vi được ghi lại không biện minh cho hành vi lớn hơn nhiều.)

Tôi có thể thấy sự hấp dẫn của hành vi mới: thay vì hai cách để thoát khỏi chủ đề chính ("thoát ra và chờ chủ đề" và "thoát ngay lập tức"), chỉ có một, vì sys.exit về cơ bản là giống hệt nhau để trở về từ hàm trên cùng. Tuy nhiên, đó là một thay đổi phá vỡ và phân kỳ từ hành vi được ghi lại, điều này vượt xa điều đó.

Do sự thay đổi này, sau sys.exit từ trình xử lý tín hiệu ở trên, trình thông dịch ngồi xung quanh chờ chủ đề thoát ra và sau đó chạy các trình xử lý atexit sau khi thực hiện. Vì nó là trình xử lý tự nó báo cho các luồng thoát ra, kết quả là một bế tắc.

+1

Rất cám ơn, Glenn. Bây giờ tôi biết những gì để tìm, tôi tìm thấy các báo cáo vấn đề Python có liên quan [ở đây] (http://bugs.python.org/issue1722344). Tôi đồng ý rằng đó là một sự thay đổi lớn đáng lẽ phải được thực hiện trong nhiều hơn một bản phát hành nhỏ. –

0

Tôi không chắc chắn nếu điều này đã hoàn toàn thay đổi, nhưng đây là cách tôi đã atexit tôi thực hiện trong 2.6.5


atexit.register(goodbye) 

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

kể từ 2.6 atexit.register có thể được sử dụng làm trang trí. – lostincode

+0

Hmm, cũng thật kỳ quặc. Bạn có chắc là bạn đang chạy cùng một mã và nó không được lưu trữ ở một nơi khác hoặc một cái gì đó kỳ lạ như thế? – Falmarri

3

Thoát khỏi do đến một tín hiệu được không giống như thoát khỏi trong số trình xử lý tín hiệu. Bắt tín hiệu và thoát với sys.exit là lối thoát sạch, không phải lối ra do bộ xử lý tín hiệu. Vì vậy, có, tôi đồng ý rằng nó sẽ chạy xử lý atexit ở đây - ít nhất là về nguyên tắc.

Tuy nhiên, có điều gì đó phức tạp về trình xử lý tín hiệu: chúng hoàn toàn không đồng bộ. Chúng có thể làm gián đoạn luồng chương trình bất cứ lúc nào, giữa bất kỳ mã opcode VM nào. Lấy mã này, ví dụ. (Hãy đối xử này là hình thức tương tự như mã của bạn ở trên, tôi đã bỏ qua mã cho ngắn gọn.)

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ó một vấn đề nghiêm trọng ở đây: một tín hiệu có thể được nhận trong khi run() là (ví dụ^C). giữ lock. Nếu điều đó xảy ra, trình xử lý tín hiệu của bạn sẽ được chạy bằng khóa vẫn được giữ. Sau đó, nó sẽ đợi test_loop thoát ra và nếu chuỗi đó đang chờ khóa, bạn sẽ bế tắc.

Đây là toàn bộ danh mục sự cố và đó là lý do tại sao một số của API không cho phép gọi chúng từ bên trong trình xử lý tín hiệu. Thay vào đó, bạn nên thiết lập một cờ để báo cho chủ đề chính tắt vào một thời điểm thích hợp.

do_shutdown = False 
def close_handler(signum, frame): 
    global do_shutdown 
    do_shutdown = True 
    print('caught signal') 

def run(): 
    while not do_shutdown: 
     ... 

sở thích của tôi là để tránh thoát khỏi chương trình với sys.exit hoàn toàn và để làm rõ ràng dọn dẹp tại các điểm thoát chính (ví dụ. Cuối run()), nhưng bạn có thể sử dụng atexit đây nếu bạn muốn .

Các vấn đề liên quan