Разрешение Ctrl-C прерывать C-расширение Python

Я запускаю вычислительно тяжелое моделирование в (самодельных) расширениях python на основе C. Иногда я ошибаюсь и хочу прервать симуляцию. Однако Ctrl-C, похоже, не имеет никакого эффекта (кроме печати ^C на экране), поэтому я должен убить процесс с помощью kill или системного монитора.

Насколько я понимаю, python просто ждет завершения расширения C и не взаимодействует с ним в это время.

Есть ли способ заставить это работать?

Обновление. Основными ответами (для моей конкретной проблемы) оказались: 1. Перепишите код, чтобы регулярно передавать управление обратно вызывающему абоненту (ответ Разрешение Ctrl-C прерывать C-расширение Python ниже), или 2. Используйте PyErr_CheckSignals() (ответьте https://stackoverflow.com/a/33652496/423420 ниже)


person Michael Clerx    schedule 05.02.2013    source источник
comment
См. stackoverflow.com/questions/ 1112343 /   -  person Mihai8    schedule 05.02.2013
comment
Прочтите signal-safety (7), это чрезвычайно соответствующие   -  person Basile Starynkevitch    schedule 20.11.2019


Ответы (4)


Я бы переделал расширения C, чтобы они не работали в течение длительного периода.

Итак, разделите их на более элементарные шаги (каждый выполняется в течение короткого периода времени, например, от 10 до 50 миллисекунд), и пусть эти более элементарные шаги будут вызываться кодом Python.

стиль прохождения продолжения может быть уместным для понимания как стиль программирования ...

person Basile Starynkevitch    schedule 05.02.2013
comment
Извините, в данном случае это вообще не вариант :) Это симуляция с огромным количеством шагов и скорость очень важна. Взаимодействие с python на каждом этапе (или даже через регулярные промежутки времени) снизит эффективность. - person Michael Clerx; 05.02.2013
comment
Попробуйте объединить шаги во что-нибудь на несколько миллисекунд. Тогда накладные расходы на переход на Python незначительны ... - person Basile Starynkevitch; 05.02.2013
comment
Об этом, конечно, стоит подумать, но это действительно вызывает множество проблем с управлением памятью и т. Д. Спасибо! - person Michael Clerx; 05.02.2013
comment
Еще не реализовал, но это определенно в моем списке дел. Еще раз спасибо! - person Michael Clerx; 11.02.2013

Однако Ctrl-C, похоже, не имеет никакого эффекта.

Ctrl-C в оболочке отправляет SIGINT группе процессов переднего плана. python при получении сигнала устанавливает флаг в коде C. Если ваше расширение C работает в основном потоке, обработчик сигналов Python не будет запускаться (и, следовательно, вы не увидите KeyboardInterrupt исключение на Ctrl-C), если вы не вызовете _ 6_, который проверяет флаг (это означает: он не должен замедлять вас) и запускает обработчики сигналов Python, если это необходимо или если ваше моделирование позволяет коду Python выполняться (например, если симуляция использует обратные вызовы Python). Вот пример кода модуля расширения для CPython, созданного с использованием pybind11, предложенного @Matt:

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 включены

person jfs    schedule 11.11.2015
comment
Есть ли способ на чистом питоне обнаружить SIGINT в обратном вызове, main.py - ›долго работающий scipy-оптимизатор, который вызывает общую библиотеку -› callback.py? (Могу я задать новый вопрос?) - person denis; 17.10.2018
comment
@denis: способ обнаружения SIGINT на чистом питоне - ›KeyboardInterrupt вызывается в основном потоке (с обработчиком сигнала по умолчанию). - person jfs; 17.10.2018
comment
Это должен быть принятый ответ. Есть законные причины, по которым вам может потребоваться дождаться переменной условия или чего-то еще, если вы выполняете блокировку ввода-вывода, которую невозможно избежать. Вы не всегда можете изменить архитектуру вокруг этого. - person mmx; 23.01.2020
comment
Я нашел это полезным: pybind11.readthedocs.io/en/stable/ - person Matt; 01.04.2020

Python имеет обработчик сигналов, установленный на SIGINT, который просто устанавливает флаг, который проверяется основным циклом интерпретатора. Чтобы этот обработчик работал правильно, интерпретатор Python должен запускать код Python.

Вам доступны несколько вариантов:

  1. Используйте _2 _ / _ 3_, чтобы освободить GIL вокруг вашего кода расширения C. Вы не можете использовать какие-либо функции Python, если не удерживаете GIL, но код Python (и другой код C) может выполняться одновременно с вашим потоком C (настоящая многопоточность). Отдельный поток Python может выполняться вместе с расширением C и перехватывать сигналы Ctrl + C.
  2. Настройте свой собственный SIGINT обработчик и вызовите исходный обработчик сигнала (Python). Затем ваш обработчик SIGINT может сделать все, что ему нужно, чтобы отменить код расширения C и вернуть управление интерпретатору Python.
person nneonneo    schedule 05.02.2013

Существует альтернативный способ решения этой проблемы, если вы не хотите, чтобы ваше расширение C (или ctypes DLL) было привязано к Python, например, в случае, когда вы хотите создать библиотеку C с привязками на нескольких языках, вы должны разрешить Расширение C для работы в течение длительного времени, и вы можете изменить расширение C:

Включите заголовок сигнала в расширение C.

#include <signal.h>

Создайте typedef обработчика сигнала в расширении C.

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 выполнит ожидаемое действие, например вызовет KeyboardInterrupt для SIGINT.

person Tom N.    schedule 03.11.2018
comment
Прочтите безопасность сигналов (7) - person Basile Starynkevitch; 20.11.2019