2013-05-27 45 views
11

Có cách nào để ngắt (Ctrl+C) một tập lệnh Python dựa trên vòng lặp được nhúng trong phần mở rộng của Cython không?Cython, Python và KeyboardInterrupt bị bỏ qua

Tôi có kịch bản python sau:

def main(): 

    # Intantiate simulator 
    sim = PySimulator() 
    sim.Run() 

if __name__ == "__main__": 
    # Try to deal with Ctrl+C to abort the running simulation in terminal 
    # (Doesn't work...) 
    try: 
     sys.exit(main()) 
    except (KeyboardInterrupt, SystemExit): 
     print '\n! Received keyboard interrupt, quitting threads.\n' 

này chạy một vòng lặp là một phần của một phần mở rộng C++ Cython. Sau đó, trong khi nhấn Ctrl+C, số KeyboardInterrupt bị ném nhưng bị bỏ qua và chương trình sẽ tiếp tục cho đến khi kết thúc mô phỏng.

Công việc xung quanh tôi thấy, là để xử lý các ngoại lệ từ bên trong phần mở rộng bằng cách bắt các tín hiệu SIGINT:

#include <execinfo.h> 
#include <signal.h> 

static void handler(int sig) 
{ 
    // Catch exceptions 
    switch(sig) 
    { 
    case SIGABRT: 
     fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr); 
     break; 
    case SIGFPE: 
     fputs("Caught SIGFPE: arithmetic exception, such as divide by zero\n", 
      stderr); 
     break; 
    case SIGILL: 
     fputs("Caught SIGILL: illegal instruction\n", stderr); 
     break; 
    case SIGINT: 
     fputs("Caught SIGINT: interactive attention signal, probably a ctrl+c\n", 
      stderr); 
     break; 
    case SIGSEGV: 
     fputs("Caught SIGSEGV: segfault\n", stderr); 
     break; 
    case SIGTERM: 
    default: 
     fputs("Caught SIGTERM: a termination request was sent to the program\n", 
      stderr); 
     break; 
    } 
    exit(sig); 

} 

Sau đó:

signal(SIGABRT, handler); 
signal(SIGFPE, handler); 
signal(SIGILL, handler); 
signal(SIGINT, handler); 
signal(SIGSEGV, handler); 
signal(SIGTERM, handler); 

Tôi không thể làm công việc này từ Python hoặc ít nhất là từ Cython? Khi tôi sắp mở rộng phần mở rộng của mình trong Windows/MinGW, tôi sẽ đánh giá cao việc có một cái gì đó ít Linux cụ thể hơn.

Trả lời

13

Bạn cần phải kiểm tra định kỳ cho tín hiệu chờ giải quyết, ví dụ, trên mỗi lần lặp thứ N của vòng lặp mô phỏng:

from cpython.exc cimport PyErr_CheckSignals 

cdef Run(self): 
    while True: 
     # do some work 
     PyErr_CheckSignals() 

PyErr_CheckSignals sẽ chạy bộ xử lý tín hiệu cài đặt với signal mô-đun (điều này bao gồm nâng cao KeyboardInterrupt nếu cần).

PyErr_CheckSignals khá nhanh, bạn có thể gọi nó thường xuyên. Lưu ý rằng nó nên được gọi từ thread chính, bởi vì Python chạy trình xử lý tín hiệu trong luồng chính. Gọi nó từ chủ đề công nhân không có hiệu lực.

Giải thích

Kể từ khi tín hiệu được chuyển giao không đồng bộ vào những thời điểm không thể đoán trước, nó là vấn đề để chạy bất kỳ mã có ý nghĩa trực tiếp từ bộ xử lý tín hiệu. Do đó, Python xếp hàng tín hiệu đến. Hàng đợi được xử lý sau này như là một phần của vòng lặp thông dịch viên.

Nếu mã của bạn được biên dịch đầy đủ, vòng lặp thông dịch sẽ không bao giờ được thực hiện và Python không có cơ hội kiểm tra và chạy các trình xử lý tín hiệu xếp hàng đợi.

+0

+1 để giải thích. Có 'PyErr_CheckSignals()' xử lý Ctrl-C trên Windows? Tôi đã chọn không tham gia kiểm tra tín hiệu ngầm bằng vòng lặp 'while sim.running: time.sleep (1)' trong chuỗi chính hoạt động miễn là mã Cython trong một luồng khác phát hành GIL theo định kỳ. – jfs

+0

Có, nó hoạt động trên Windows như mong đợi. –

+0

Tôi đã sử dụng '" chọn không tham gia "' sai. Tôi đã có nghĩa là đối diện: 's/chọn không tham gia/chọn để làm /' ở trên. – jfs

1

Phát hành GIL khi Cython chạy các phần không giao tiếp với Python, chạy vòng lặp trong luồng chính (ngủ hoặc kiểm tra trạng thái mô phỏng) và gọi sim.Stop() (có thể đặt một số cờ mà mô phỏng của bạn có thể kiểm tra định kỳ) trong except đóng.

+0

Ok, điều đó có vẻ có ý nghĩa (ngay cả khi tôi đã bí mật nhảy lên đó sẽ là một giải pháp dễ dàng ;-). Cảm ơn vì lời khuyên. –

3

Nếu bạn đang cố gắng xử lý KeyboardInterrupt trong mã phát hành GIL (ví dụ, vì nó sử dụng cython.parallel.prange), bạn sẽ cần phải lấy lại GIL để gọi PyErr_CheckSignals.Đoạn sau (chuyển thể từ câu trả lời @ Nikita-nemkin của trên) minh họa những gì bạn cần làm:

from cpython.exc cimport PyErr_CheckSignals 
from cython.parallel import prange 

cdef Run(self) nogil: 
    with nogil: 
     for i in prange(1000000) 
      # do some work but check for signals every once in a while 
      if i % 10000 == 0: 
       with gil: 
        PyErr_CheckSignals() 
+0

Thật vô nghĩa khi gọi PyErr_CheckSignals trừ khi bạn đang ở trong chuỗi chính. – jfs

0

Vâng, sử dụng các macro sig_on and sig_off từ gói cysignals:

from cysignals.signals cimport sig_on, sig_off 

def foo(): 
    sig_on() 
    call_c_code_that_takes_long() 
    sig_off() 

Các macro sig_onsig_off được khai báo là hàm in cysignals/signals.pxd và được định nghĩa là macro in cysignals/macros.h về macro _sig_on_ (được xác định theo các chức năng _sig_on_prejmp_sig_on_postjmp) và hàm _sig_off_. Trình xử lý tín hiệu cho các ngắt bàn phím (SIGINT) được cài đặt here và lý do thực hiện được phác thảo here.

Kể từ cysignals == 1.6.5, chỉ hỗ trợ hệ thống POSIX. Có thể sử dụng conditional compilation của Cython để thực hiện theo cách tiếp cận này ở bất kỳ nơi nào có sẵn cysignals và cũng cho phép biên dịch trên các hệ thống không POSIX (không có Ctrl-C hoạt động trên các hệ thống đó).

Trong kịch bản setup.py:

compile_time_env = dict(HAVE_CYSIGNALS=False) 
# detect `cysignals` 
if cysignals is not None: 
    compile_time_env['HAVE_CYSIGNALS'] = True 
... 
c = cythonize(..., 
       compile_time_env=compile_time_env) 

và trong *.pyx tập tin có liên quan:

IF HAVE_CYSIGNALS: 
    from cysignals.signals cimport sig_on, sig_off 
ELSE: 
    # for non-POSIX systems 
    noop = lambda: None 
    sig_on = noop 
    sig_off = noop 

Xem thêm this answer.