无法在多线程 C 应用程序嵌入中终止多处理池 Python

Can't kill multiprocessing pool in a multithreaded C application embedding Python

OS: linux
Python版本:3.6

我正在尝试使用 Python 运行时扩展 C 应用程序。 C 应用程序使用 pthread,我尝试在 Python 运行时使用 multiprocessing forkserver 但遇到了问题。当我尝试用 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 信号是如何工作的。我试图用信号模块在 python 主进程中捕获 SIGINT 信号,但似乎主进程没有接收到信号。我怎样才能让这个应用程序在 SIGINT 上优雅地死去而不会永远挂起?


通过阅读 ViKiG answer,我意识到我可以首先在工作进程中捕获 KeyboardInterrupt(或 SIGINT) 异常并将一些哨兵值发送到主进程以通知异常和关闭应用程序。

在浏览了 CPython forkserver 实现后,我可能得出结论,库的作者故意让主进程忽略 SIGINT。我想,目前,推荐的方法是在工作进程中捕获异常,而不是在主进程中。

我更改了 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")

Py_Initialize() 将安装 python 自己的信号处理程序,改为调用 Py_InitializeEx(0)

void Py_InitializeEx(int initsigs)

This function works like Py_Initialize() if initsigs is 1. If initsigs is 0, it skips initialization registration of signal handlers, which might be useful when Python is embedded.

在其 doc, and cpython source 上查看更多信息。