GNU/Linux >> Linux の 問題 >  >> Linux

Ctrl-C で Python C 拡張を中断できるようにする

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
    

    Linux
    1. ターミナルでCtrl-cを入力すると、フォアグラウンドジョブが完了するまで終了しないのはなぜですか?

    2. Windowsにpython-novaclientをインストールします

    3. IIS7.5でPythonをセットアップする

    1. Python - すべてのシグナルをトラップする

    2. Python 用の tkinter をインストールする

    3. C でのシグナル キューイング

    1. Python には同期がありますか?

    2. Linux での Ctrl + C 割り込みイベント処理

    3. Python で C コードを使用できますか?