有两个程序通过命名管道相互通信。一种在 C++ 中,一种在 Python 中。此外,第一个启动第二个(以标准方式,通过 fork + exec)。
父程序交流区(C++):
// ...
int pipeDescr;
std::string outputPipeName{"inputPipe"};
std::string inputPipeName{"outputPipe"};
char *message = new char[BUFSIZE];
// ...
while (true)
{
int bytesNumber = 0;
// ...
if ((pipeDescr = open(outputPipeName.c_str(), O_WRONLY)) <= 0)
break;
bytesNumber = write(pipeDescr, message, strlen(message));
if (bytesNumber <= 0)
break;
close(pipeDescr);
message[0] = '\0';
if ((pipeDescr = open(inputPipeName.c_str(), O_RDONLY)) <= 0)
break;
bytesNumber = read(pipeDescr, message, BUFSIZE);
if (bytesNumber <= 0)
break;
close(pipeDescr);
// ...
}
watchDog函数运行在一个单独的线程中,负责Python中子应用程序的运行。
void watchDog(int clientSocket, pid_t pid, bool &stopWatchDog)
{
while (true)
{
// Дочерняя программа завершилась с ошибкой
if (waitpid(pid, NULL, WNOHANG) != 0)
{
// ...
}
// Родительская программа закрывается
mutexClosing.lock();
if (closing)
{
mutexClosing.unlock();
if (waitpid(pid, NULL, WNOHANG) == 0)
{
kill(pid, SIGTERM);
waitpid(pid, NULL, 0);
}
break;
}
mutexClosing.unlock();
// Программное отключение WatchDog-а
mutexWatchDog.lock();
if (stopWatchDog)
{
if (waitpid(pid, NULL, WNOHANG) == 0)
{
kill(pid, SIGTERM);
waitpid(pid, NULL, 0);
}
mutexWatchDog.unlock();
break;
}
mutexWatchDog.unlock();
}
}
这样做有以下三种结果:
子程序崩溃
父程序的终止
在不终止父程序的情况下终止子程序
最后两个选项要求子程序正常终止,所以我向它发送一个 SIGTERM 信号并等待它完成。
子程序通信部分(Python):
def sigterm_handler(signal, frame):
print('\nGot sigterm!\n')
sys.exit(0)
def main():
input_pipe_name = "inputPipe"
output_pipe_name = "outputPipe"
# ...
signal.signal(signal.SIGTERM, sigterm_handler)
# ...
while True:
pipe_descr = os.open(input_pipe_name, os.O_RDONLY)
request = os.read(pipe_descr, 10000)
os.close(pipe_descr)
reply = work_func(request)
pipe_descr = os.open(output_pipe_name, os.O_WRONLY)
os.write(pipe_descr, bytes(reply, 'UTF-8'))
os.close(pipe_descr)
我在代码中添加了 SIGTERM 信号的处理。但是,发送此信号时,不会调用 sigterm_handler 函数。
但!如果你写这样的东西:
def main():
signal.signal(signal.SIGTERM, sigterm_handler)
while True:
print('waiting...')
time.sleep(2)
该函数将被调用。
请告诉我如何解决这个问题!
当接收到信号时,C 处理程序设置一个标志并立即退出(下文中,我描述了 Python 实现)。用 Python 编写的处理程序仅在控制返回主解释器线程时执行,这会稍后发生(例如在以下字节码中)或永远不会发生。引用官方文档:
何时返回控制权取决于当时执行的位置(不同平台上不同版本的 Python 上的不同函数可能有不同的行为)。例如,
os.open()在 Python 3.5 的 POSIX 系统上,它归结为:Py_BEGIN_ALLOW_THREADSopen(2)该宏释放了 GIL,它允许其他线程在当前线程被系统调用阻塞时执行 Python 代码。设置它的处理程序时,
signal.signal()调用会重置SA_RESTART标志,这样系统调用就会open(2)被信号和 return 中断EINTR,在这种情况下调用PyErr_CheckSignals()一个函数,除非调用来自程序的主线程,否则什么都不做。在主线程PyErr_CheckSignals()中,它检查是否有信号(通过 C 处理程序设置的标志)并调用用 Python 编写的处理程序。如果处理程序抛出异常,它PyErr_CheckSignals()返回非零值并且循环中断,导致从主线程调用os.open时 Python 代码中的调用站点出现异常(否则返回)。os.openPyErr_CheckSignals()0在其他情况下,可能有很多选项:由GIL释放/不释放(在C代码中),阻塞调用本身是否被中断/自动重启(可能取决于平台,C库,Python版本),是否是在主线程中调用
PyErr_CheckSignals(),无论它是否在某处重置,然后标记信号发生在调用处理程序之前(signal(SIGINT, custom_handler)在 Python 2.7 上的 Windows 上不起作用)。如果您不能将阻塞的 C 代码更改为
PyErr_CheckSignals()像os.open接收信号时那样调用,那么要解决这个问题:在后台线程和主线程上调用阻塞代码,以短时间间隔休眠(这不会如果你的 CPython 扩展没有释放 GIL,那将无济于事,例如re,一个模块可能会这样做):请注意,这与添加
time.sleep(1)到循环的建议不同,后者适用于FIFO(7)。如果信号发生在您的 fifo 循环中的调用之外time.sleep(1),那么问题将仍然存在。解决方法之所以有效,是因为 fifo 循环在后台线程上运行,而主线程只是间歇性休眠。在 Python 3 中,它可以
background_thread.join()不被使用timeout(在 Python 2 中,这个.join()没有被信号中断)。在
while True:您需要添加 之后time.sleep(1),因为您需要稍等片刻才能向该过程发出信号。ps 摘自评论