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

1

ОС: 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 forkserver, я пришел к выводу, что автор библиотеки намеренно сделал основной процесс игнорированием SIGINT. На мой взгляд, в настоящее время рекомендуется использовать исключение в рабочих процессах, а не в основном.

Показать ещё 1 комментарий
Теги:
multithreading
multiprocessing
python-c-api

3 ответа

1

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

void Py_InitializeEx (int initsigs)

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

подробнее о его документе и источнике cpython.

1

Я изменил функцию 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);
  • 0
    Спасибо за ваш эксперимент, но я только что понял, что написал немного неправильный пример для моего случая. Мне очень жаль, но позвольте мне, пожалуйста, немного изменить пример (бесконечный цикл должен быть в основном процессе, а не в рабочем процессе)
  • 0
    Спасибо за ваш комментарий, но я думаю, что это не совсем то, как вы делаете изящную среду исполнения Python.
0

Оказалось, что мне не нужно ломать исключение в основном процессе. Я решил проблему, поймав исключение 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")

Ещё вопросы

Сообщество Overcoder
Наверх
Меню