我在(自制的)基于C的python扩展中运行一些计算量很大的模拟。偶尔我会弄错,想终止模拟。但是,Ctrl-C似乎没有任何效果(除了将^C
打印到屏幕之外,因此我必须使用kill
或系统监视器终止进程。
据我所知,python只是等待C扩展完成,并且在此期间没有与它进行真正的通信。
有没有办法让这项工作?
答案 0 :(得分:6)
Python在SIGINT
上安装了一个信号处理程序,它只是设置一个由主解释器循环检查的标志。要使此处理程序正常工作,Python解释器必须运行Python代码。
您有几种选择:
Py_BEGIN_ALLOW_THREADS
/ Py_END_ALLOW_THREADS
在C扩展程序代码周围发布GIL。不保存GIL时不能使用任何Python函数,但Python代码(和其他C代码)可能与C线程并行运行(真正的多线程)。一个单独的Python线程可以与C扩展一起执行并捕获Ctrl + C信号。SIGINT
处理程序并调用原始(Python)信号处理程序。然后,您的SIGINT
处理程序可以执行任何操作,以取消C扩展代码并将控制权返回给Python解释器。答案 1 :(得分:5)
但是,Ctrl-C似乎没有任何效果
Ctrl-C
in the shell sends SIGINT
to the foreground process group。接收信号时python
在C代码中设置一个标志。如果您的C扩展在主线程中运行,那么将不会运行Python信号处理程序(因此您不会在KeyboardInterrupt
上看到Ctrl-C
异常),除非您调用PyErr_CheckSignals()
检查标志(它意味着:它不应该让你慢下来)并在必要时运行Python信号处理程序,或者如果你的模拟允许Python代码执行(例如,如果模拟使用Python回调)。如果扩展在后台线程中运行,则释放GIL就足够了(允许Python代码在主线程中运行,使信号处理程序能够运行)。
答案 2 :(得分:2)
我会重新设计C扩展,以便它们不会运行很长时间。
因此,将它们分成更基本的步骤(每个步骤运行一小段时间,例如10到50毫秒),并使用Python代码调用这些更基本的步骤。
作为一种编程风格,continuation passing style可能与理解相关......
答案 3 :(得分:2)
如果您不希望将C扩展(或ctypes DLL)绑定到Python,则有另一种方法可以解决此问题,例如,您要创建带有多种语言绑定的C库的情况,必须允许您的C Extension长时间运行,然后您可以修改C Extension:
在C扩展中包含信号头。
#include <signal.h>
在C扩展程序中创建信号处理程序typedef。
typedef void (*sighandler_t)(int);
在C扩展中添加信号处理程序,该信号处理程序将执行中断任何长时间运行的代码(设置停止标志等)所需的操作,并保存现有的Python信号处理程序。
sighandler_t old_sig_int_handler = signal(SIGINT, your_sig_handler);
sighandler_t old_sig_term_handler = signal(SIGTERM, your_sig_handler);
每当C扩展返回时,恢复现有的信号处理程序。此步骤可确保重新应用Python信号处理程序。
signal(SIGINT, old_sig_int_handler);
signal(SIGTERM, old_sig_term_handler);
如果长时间运行的代码被中断(标志等),请使用指示信号编号的返回码将控制返回给Python。
return SIGINT;
在Python中,发送在C扩展中收到的信号。
import os
import signal
status = c_extension.run()
if status in [signal.SIGINT, signal.SIGTERM]:
os.kill(os.getpid(), status)
Python将执行您期望的操作,例如为SIGINT引发KeyboardInterrupt。