ОС: 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
. На мой взгляд, в настоящее время рекомендуется использовать исключение в рабочих процессах, а не в основном.
Py_Initialize()
установит собственный обработчик Py_InitializeEx(0)
вместо этого вызовет Py_InitializeEx(0)
:
void Py_InitializeEx (int initsigs)
Эта функция работает как Py_Initialize(), если initsigs равно 1. Если initsigs равно 0, он пропускает регистрацию инициализации обработчиков сигналов, что может быть полезно при встраивании Python.
подробнее о его документе и источнике cpython.
Я изменил функцию 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);
Оказалось, что мне не нужно ломать исключение в основном процессе. Я решил проблему, поймав исключение 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")