Не удается убить многопроцессорный пул в многопоточном приложении C, встраивающем Python

ОС: Linux
Версия Python: 3.6

Я пытаюсь расширить приложение C с помощью среды выполнения Python. Приложение C использует pthread, а я пытался использовать multiprocessing forkserver в среде выполнения Python, но столкнулся с проблемой. Когда я пытаюсь завершить программу сигналом SIGINT (нажав Ctrl+C в терминале), рабочие процессы убиваются, но основная программа зависает.

Вот игрушечная программа, которая выдает ту же проблему.

#include <Python.h>
#include <pthread.h>

void * thread_start(void *unsed)
{
    PyObject *fs_mod = PyImport_AddModule("fs");
    PyObject *apply_fn = PyObject_GetAttrString(fs_mod, "apply");
    PyObject *job_fn = PyObject_GetAttrString(fs_mod, "job");
    PyObject *job_args = Py_BuildValue("()");
    PyObject_CallFunctionObjArgs(apply_fn, job_fn, job_args, NULL);
    printf("finished\n");
    return NULL;
}

int main(){
    Py_Initialize();
    PyRun_SimpleString(
        "import sys; sys.path.append('...');"
        "sys.argv=['a.out'];"  // prepare a dummy argument to avoid error in forkserver
        "import fs\n"
        "if __name__ == '__main__': fs.init()");

    while(1){
        pthread_t thread;
        pthread_create(&thread, 0, &thread_start, NULL);
        printf("joing\n");
        pthread_join(thread, 0);
    }
}
import multiprocessing as mp

pool = None


def job():
    import time
    print("running..")
    time.sleep(5)

def init():
    global pool
    mp.set_start_method('forkserver')
    pool = mp.Pool(1)

def apply(*args):
    global pool
    return pool.apply(*args)

Я точно не знаю, как работает сигнал Linux. Я пытался поймать сигнал SIGINT в основном процессе Python с модулем сигнала, но кажется, что основной он не получает сигнал. Как я могу заставить это приложение изящно умереть на SIGINT, не зависая навсегда?


Прочитав ответ ViKiG, я понял, что могу сначала поймать исключение KeyboardInterrupt (или SIGINT) в рабочих процессах и отправить некоторое сигнальное значение в основной процесс, чтобы уведомить об исключении и закрыть приложение.

Изучив реализацию форксервера CPython, я потенциально пришел к выводу, что автор библиотеки намеренно заставил основной процесс игнорировать файл SIGINT. Я предполагаю, что в настоящее время рекомендуется перехватывать исключение в рабочих процессах, а не в основном.


person bombs    schedule 23.08.2018    source источник
comment
Я искал тег python и SIGINT, чтобы найти ответ на этот вопрос.   -  person jwdonahue    schedule 23.08.2018
comment
Возможный дубликат Как мне захватить SIGINT в Python?   -  person jwdonahue    schedule 23.08.2018
comment
@jwdonahue Я попробовал сигнальный модуль, но не смог поймать сигнал SIGINT. Вот почему я упомянул, что «основной процесс не получает сигнал SIGINT».   -  person bombs    schedule 23.08.2018


Ответы (3)


Я изменил функцию job для обработки CTRL+C прерываний:

    def job():
        import time
        try:    
            while True:
                print("running..")
                time.sleep(5)
        except KeyboardInterrupt:
            print 'Exiting job..'

Моя тестовая программа завершается чисто после вышеуказанного изменения.

ПОСЛЕ РЕДАКТИРОВАНИЯ:

Я добавил это в свою программу C

    #include<signal.h>

    void handler() {printf("Exiting main.."); exit(0);}

Изменено main как:

    int main() {
        signal(SIGINT, handler);
person ViKiG    schedule 23.08.2018
comment
Спасибо за ваш эксперимент, но я только что понял, что написал немного неправильный пример для своего случая. Мне очень жаль, но я позвольте мне немного изменить пример (бесконечный цикл должен быть в основном процессе, а не в рабочем процессе) - person bombs; 23.08.2018
comment
Спасибо за ваш комментарий, но я думаю, что это не совсем то, как вы изящно умираете во время выполнения Python. - person bombs; 23.08.2018

Py_Initialize() установит собственный обработчик singal python, вместо этого вызовите Py_InitializeEx(0):

недействительными Py_InitializeEx (int initsigs)

Эта функция работает как Py_Initialize(), если initsigs равен 1. Если initsigs равен 0, она пропускает регистрацию инициализации обработчиков сигналов, что может быть полезно при встроенном Python.

подробнее см. в его документе и исходный код cpython.

person georgexsh    schedule 23.08.2018

Оказалось, что мне не нужно ловить исключение в основном процессе. Я решил проблему, перехватив исключение KeyboardInterrupt(или SIGINT) в рабочих процессах и отправив некоторое сигнальное значение в основной процесс, чтобы уведомить об исключении и закрыть приложение.

import multiprocessing as mp


pool = None


def job():
    try:
        import time
        print("running..")
        time.sleep(5)
        return True
    except KeyboardInterrupt:
        print("Exiting..")
        return False
...

def apply(*args):
    global pool
    ret = pool.apply(*args)
    if ret:
        return pool.apply(*args)
    else:
        print("Gracefully die")
person bombs    schedule 23.08.2018