为啥不能从 Kivy 终止这个 Python 多处理进程?

Posted

技术标签:

【中文标题】为啥不能从 Kivy 终止这个 Python 多处理进程?【英文标题】:Why can't this Python multiprocessing process be terminated from Kivy?为什么不能从 Kivy 终止这个 Python 多处理进程? 【发布时间】:2015-01-13 13:53:20 【问题描述】:

我正在尝试从 Kivy 应用程序中运行 django 开发服务器。到目前为止,这确实很有效。

现在我想允许用户在服务器运行时继续使用该程序。我的想法是为 httpd.serve_forever() 创建一个 multiprocessing.Process 以避免主程序完全锁定。做得很好。这是我的 internal_django 模块中的代码:

import multiprocessing
import os
import time

from wsgiref.simple_server import make_server

def django_wsgi_application():

    PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
    settings_module = "djangosettings"#%s.djangosettings" % PROJECT_ROOT.split(os.sep)[-1]

    os.environ.update("DJANGO_SETTINGS_MODULE":settings_module)

    from django.core.wsgi import get_wsgi_application
    application = get_wsgi_application()

    return application


class Singleton(type):
    _instances = 
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class DjangoServer():
    __metaclass__ = Singleton

    def start(self):
        self.httpd = make_server('', 8000, django_wsgi_application())
        self.server = multiprocessing.Process(target=self.httpd.serve_forever)
        self.server.start()
        print "Now serving on port 8000..."
        print "Server Process PID = %s" %self.server.pid

    def stop(self):
        print("shutdown initiated")
        print "Server Process PID = %s" %self.server.pid
        while self.server.is_alive():
            self.server.terminate()
            print("Server should have shut down")
            time.sleep(1)
        print("Server is_alive: %s" %self.server.is_alive())
        self.server.join()
        print("server process joined")



if __name__ == "__main__":
    server = DjangoServer()
    server.start()
    time.sleep(3)
    server.stop()

当我运行这段代码时,一切都按预期工作。这是控制台中显示的内容:

Now serving on port 8000...
Server Process PID = 1406
shutdown initiated
Server Process PID = 1406
Server should have shut down
Server is_alive: False
server process joined

下一步是提供一种从 Kivy 应用程序中停止服务器的方法。为此,我只想像以前一样使用我的 DjangoServer 类:

from internal_django import DjangoServer

class StartScreen(Screen): 
    def start_server(self):
        server = DjangoServer()
        server.start()


class StopScreen(Screen):  
    def stop_server(self):
        server = DjangoServer()
        server.stop()

但是这样做时,进程一旦开始就永远不会退出。我的第一个想法是 Singleton 没有按预期工作,我试图退出错误的过程。但正如您在输出中看到的,PID 是相同的。服务器收到终止命令,但只是继续工作。这是控制台的样子:

Now serving on port 8000...
Server Process PID = 1406
shutdown initiated
Server Process PID = 1406
Server should have shut down
Server should have shut down
Server should have shut down
Server should have shut down
Server should have shut down
Server should have shut down
Server should have shut down
Server should have shut down

(and so on, until i manually kill the server process)

我是否以完全错误的方式使用多处理? Kivy 是否以某种方式干扰了该过程?

【问题讨论】:

多久启动/停止一次服务器?如果您只启动一次,并在退出 Kivy 应用程序时停止,那么只需将服务器设置为守护进程,不必担心停止它。在调用 self.server.start() 之前执行此操作:self.server.daemon = True 据我所知,这将使服务器进程保持活动状态,这将导致在重新启动应用程序(或任何其他试图在相关端口上提供服务的本地服务)时阻塞连接端口。不是我想要的。每次单击相应的按钮时,都应通过用户交互来启动/停止服务器。 【参考方案1】:

我认为这里的问题可能有两个:

    信号处理程序正在拦截 Process.terminate() 发送的 TERM 请求并忽略它。要验证这一点,只需在新进程中使用 signal.getsignal(signal.SIGTERM) 并打印结果。为了规避此类问题,您可以使用 signal.signal(signal.SIGTERM, signal.SIG_DFL) 重置默认行为,但请记住,SIGTERM 被框架静音可能是有原因的(我对 Django 也不熟悉Kivy 也不行)。

    如果您使用的是 Python 2,则必须考虑如果解释器在来自线程库(锁、信号量..)的同步原语或本机 C 调用上被阻塞,则解释器不会处理信号。在这些情况下,serve_forever() 函数可能会失效(使用源的力量!)。快速检查可能是尝试在 Python 3 上运行代码并查看它是否有效。

一个快速而肮脏的解决方案是等待一小段时间,如果进程仍然存在,则发送一个 SIGKILL。

import os
import signal

process.terminate()
process.join(1)

if process.is_alive() and os.name != 'nt':
    try:
        os.kill(process.pid, signal.SIGKILL)
        process.join()
    except OSError:
        return  # process might have died while checking it

在 Windows 上,您无法以如此简单的方式终止进程,这就是我测试 os.name 的原因。

这是一种非常原始的方法,所以我宁愿建议找出问题的原因。

【讨论】:

【参考方案2】:

如果您调用terminate(),然后调用join() 并跳过while 循环会发生什么?另外,我稍微打乱了代码并将一些代码分解为_create_server()。请让我知道这是否适合您。

class DjangoServer():
    __metaclass__ = Singleton

    def _create_server(self):
        httpd = make_server('', 8000, django_wsgi_application())
        print "Now serving on port ...".format(httpd.server_port)
        httpd.serve_forever()

    def start(self):
        self.server = multiprocessing.Process(target=self._create_server)
        self.server.start()
        print "Server Process PID = %s" %self.server.pid

    def stop(self):
        print("shutdown initiated")
        print "Server Process PID = %s" %self.server.pid
        self.server.terminate()
        self.server.join()
        print("server process terminated")

【讨论】:

实际上没有while循环的代码是我的第一次尝试,我只是介绍了while循环作为一个快速的hack,以确保我的问题不是由竞争条件引起的。我确实只是使用了您的代码,但问题仍然存在(只是在加入后主进程被锁定而不是一直打印)。

以上是关于为啥不能从 Kivy 终止这个 Python 多处理进程?的主要内容,如果未能解决你的问题,请参考以下文章

Kivy:为啥 ScrollView 不能在 GridLayout 中工作?

为啥我不能在 main.py 中使用 kivy 添加图像?

安装 KIVY 时出错:为啥执行后出现错误:“python -m pip install kivy”?

BAT 文件在激活 kivy 脚本时自行终止

为啥 Kivy 需要括号,而 Python 不需要括号?

通过python中的kivy模块开发app