当有人访问特定视图时,我需要使用子流程启动长时间运行的后台进程。
我的代码:
from flask import Flask
import subprocess
app = Flask(__name__)
@app.route("/")
def index():
subprocess.Popen(["sleep", "10"])
return "hi\n"
if __name__ == "__main__":
app.run(debug=True)
这在大多数情况下效果很好。
问题在于,当进程(睡眠)结束时,ps -Af | grep sleep
会将其显示为[sleep] <defunct>
。
从我读过的内容来看,这是因为我仍然在flask
中提及了这个过程。
进程退出后是否有办法删除此引用?
我尝试了g.subprocess = subprocess.Popen(["sleep", "10"])
,并等待进程以@app.after_request(response)
结束,因此我可以在其上使用del
,但这可以防止在子进程退出之前返回响应 - 我需要它在子进程退出之前返回响应。
注意:
我需要subprocess.Popen操作是非阻塞的 - 这很重要。
答案 0 :(得分:2)
正如我在评论中所建议的那样,在Python中实现此类事情的最干净,最强大的方法之一是使用celery。
Celery需要代理传输进行消息传递,其中rabbitmq是默认的,至少有一个正在运行的进程的进程。但是,提高可读性和可扩展性的是工作者代码可以与服务器应用程序共存于同一文件或文件中。您可以像调用简单函数一样调用远程过程。
Celery可以免费处理重试,任务后事件和许多其他事情,所有内容都通过多年生产中的成熟代码加以强化。
这是重写它以便与Celery一起使用的例子:
from flask import Flask
from celery import Celery
import subprocess
app = Flask(__name__)
celery_app = Celery("test")
@celery_app.task
def run_process():
subprocess.Popen(["sleep", "5"])
@app.route("/")
def index():
run_process.delay()
return "hi\n"
if __name__ == "__main__":
app.run(debug=True, port=8080)
使用此代码,在使用默认选项运行的rabbitmq服务器的系统中(我安装了软件包,并启动了服务 - 没有任何配置。当然,在生产中你必须调整它 - 但如果一切都是在同一台服务器上,甚至可能不需要它。)
使用rabbitmq,可以使用命令行启动工作进程,例如:celery worker -A bla1.celery_app -D
(与你的Flask相同的virtualenv上的pip install celery)。然后启动烧瓶服务器,看它是否正常工作。
当然,如果你在Python本身做更多工作而不仅仅是调用外部进程,那么这将带来更多优势。它可以访问您的数据库模型,您可以执行修改其中对象的异步操作(最终触发用户响应,作为用户会话上的“flash”消息或电子邮件)
答案 1 :(得分:1)
我已经看过很多&#34;穷人的并行处理&#34;使用subprocess.Popen
并让它自由运行,但正如您所指出的那样,这通常会导致僵尸问题。
您可以在一个线程中运行您的进程(在这种情况下,不需要Popen
,如果您想在进程失败时引发异常,只需使用call
或check_call
。 call
或check_call
(或run
,因为Python 3.5)等待流程完成所以没有僵尸,并且因为你在线程中运行它你不是阻止。
import threading
def in_background():
subprocess.call(["sleep", "10"])
@app.route("/")
def index():
t = threading.Thread(target=in_background)
t.start()
return "hi\n"
注意:要等待线程完成,您必须使用t.join()
,因此您必须在t
线程对象上保留引用。
sleep
,或者它不是很有用,time.sleep(10)
也是如此(总是在当然是一个线索!)