C 拡張機能 (または ctypes DLL) を Python に関連付けたくない場合、この問題を解決する別の方法があります。たとえば、複数の言語でバインディングを使用して C ライブラリを作成する場合などです。長期間実行するための C 拡張機能。C 拡張機能を変更できます:
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 を発生させるなど、期待どおりのアクションを実行します。
<ブロック引用>
ただし、Ctrl-C は効果がないようです
Ctrl-C
シェルで SIGINT
を送信します フォアグラウンド プロセス グループに。 python
信号を受信すると、C コードでフラグが設定されます。 C 拡張機能がメイン スレッドで実行される場合、Python シグナル ハンドラは実行されません (したがって、KeyboardInterrupt
は表示されません)。 Ctrl-C
の例外 ) PyErr_CheckSignals()
を呼び出さない限り これはフラグをチェックし (つまり、速度を落とさないことを意味します)、必要に応じて、またはシミュレーションで Python コードの実行が許可されている場合 (たとえば、シミュレーションで Python コールバックが使用されている場合) に Python シグナル ハンドラーを実行します。 @Matt によって提案された pybind11 を使用して作成された CPython の拡張モジュールのコード例を次に示します。
PYBIND11_MODULE(example, m)
{
m.def("long running_func", []()
{
for (;;) {
if (PyErr_CheckSignals() != 0)
throw py::error_already_set();
// Long running iteration
}
});
}
拡張機能がバックグラウンド スレッドで実行される場合は、GIL を解放するだけで十分です (シグナル ハンドラーの実行を可能にするメイン スレッドで Python コードを実行できるようにするため)。 PyErr_CheckSignals()
常に 0
を返します バックグラウンド スレッドで。
関連:Cython、Python、および KeybordInterrupt が組み込まれています
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 インタープリターに戻すために必要なことは何でも行うことができます。エレガントではありませんが、C++ での外部ライブラリの呼び出しも中断し、実行中の子プロセスを強制終了する唯一の方法です。
#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