Es gibt eine alternative Möglichkeit, dieses Problem zu lösen, wenn Sie nicht möchten, dass Ihre C-Erweiterung (oder ctypes-DLL) an Python gebunden ist, z. B. wenn Sie eine C-Bibliothek mit Bindungen in mehreren Sprachen erstellen möchten, müssen Sie Ihre zulassen C-Erweiterung für längere Zeit laufen, und Sie können die C-Erweiterung ändern:
Fügen Sie den Signalheader in die C-Erweiterung ein.
#include <signal.h>
Erstellen Sie eine Signal-Handler-Typedef in der C-Erweiterung.
typedef void (*sighandler_t)(int);
Fügen Sie Signal-Handler in der C-Erweiterung hinzu, die die Aktionen ausführen, die erforderlich sind, um lang laufenden Code zu unterbrechen (Setzen eines Stopp-Flags usw.), und speichern Sie die vorhandenen Python-Signal-Handler.
sighandler_t old_sig_int_handler = signal(SIGINT, your_sig_handler);
sighandler_t old_sig_term_handler = signal(SIGTERM, your_sig_handler);
Stellen Sie die vorhandenen Signalhandler wieder her, wenn die C-Erweiterung zurückkehrt. Dieser Schritt stellt sicher, dass die Python-Signalhandler erneut angewendet werden.
signal(SIGINT, old_sig_int_handler);
signal(SIGTERM, old_sig_term_handler);
Wenn der lange laufende Code unterbrochen wird (Flag usw.), geben Sie die Kontrolle mit einem Rückgabecode zurück, der die Signalnummer angibt.
return SIGINT;
Senden Sie in Python das empfangene Signal in der C-Erweiterung.
import os
import signal
status = c_extension.run()
if status in [signal.SIGINT, signal.SIGTERM]:
os.kill(os.getpid(), status)
Python führt die erwartete Aktion aus, z. B. das Auslösen eines KeyboardInterrupt für SIGINT.
Strg-C scheint jedoch keine Wirkung zu haben
Ctrl-C
in der Shell sendet SIGINT
zur Vordergrundprozessgruppe. python
beim Empfang des Signals wird ein Flag im C-Code gesetzt. Wenn Ihre C-Erweiterung im Hauptthread ausgeführt wird, wird kein Python-Signalhandler ausgeführt (und daher sehen Sie KeyboardInterrupt
nicht Ausnahme auf Ctrl-C
), es sei denn, Sie rufen PyErr_CheckSignals()
an das das Flag überprüft (es bedeutet:es sollte Sie nicht verlangsamen) und Python-Signalhandler ausführen, falls erforderlich oder wenn Ihre Simulation die Ausführung von Python-Code zulässt (z. B. wenn die Simulation Python-Callbacks verwendet). Hier ist ein Codebeispiel eines Erweiterungsmoduls für CPython, das mit pybind11 erstellt wurde, vorgeschlagen von @Matt:
PYBIND11_MODULE(example, m)
{
m.def("long running_func", []()
{
for (;;) {
if (PyErr_CheckSignals() != 0)
throw py::error_already_set();
// Long running iteration
}
});
}
Wenn die Erweiterung in einem Hintergrundthread ausgeführt wird, reicht es aus, GIL freizugeben (damit Python-Code im Hauptthread ausgeführt werden kann, der die Ausführung der Signalhandler ermöglicht). PyErr_CheckSignals()
gibt immer 0
zurück in einem Hintergrundthread.
Verwandte:Cython, Python und KeybordInterrupt ingored
Python hat einen Signal-Handler auf SIGINT
installiert die einfach ein Flag setzt, das von der Hauptinterpreterschleife überprüft wird. Damit dieser Handler ordnungsgemäß funktioniert, muss der Python-Interpreter Python-Code ausführen.
Ihnen stehen einige Optionen zur Verfügung:
- Verwenden Sie
Py_BEGIN_ALLOW_THREADS
/Py_END_ALLOW_THREADS
die GIL um Ihren C-Erweiterungscode freizugeben. Sie können keine Python-Funktionen verwenden, wenn Sie die GIL nicht besitzen, aber Python-Code (und anderer C-Code) kann gleichzeitig mit Ihrem C-Thread ausgeführt werden (echtes Multithreading). Ein separater Python-Thread kann neben der C-Erweiterung ausgeführt werden und Strg+C-Signale abfangen. - Erstellen Sie Ihren eigenen
SIGINT
Handler und rufen Sie den ursprünglichen (Python) Signalhandler auf. IhrSIGINT
Der Handler kann dann alles tun, was er tun muss, um den C-Erweiterungscode abzubrechen und die Kontrolle an den Python-Interpreter zurückzugeben.
Nicht elegant, aber der einzige Ansatz, den ich gefunden habe, der auch externe Bibliotheksaufrufe in C++ unterbricht und alle laufenden untergeordneten Prozesse beendet.
#include <csignal>
#include <pybind11/pybind11.h>
void catch_signals() {
auto handler = [](int code) { throw std::runtime_error("SIGNAL " + std::to_string(code)); };
signal(SIGINT, handler);
signal(SIGTERM, handler);
signal(SIGKILL, handler);
}
PYBIND11_MODULE(example, m)
{
m.def("some_func", []()
{
catch_signals();
// ...
});
}
import sys
from example import some_func
try:
some_func()
except RuntimeError as e:
if "SIGNAL" in str(e):
code = int(str(e).rsplit(" ", 1)[1])
sys.exit(128 + code)
raise