多线程

flask 默认使用多进程处理请求,因此,是支持并发的。比如两个调用 a.html 和 b.html,

  • 请求 a.html 未运行完成,在浏览访问 b.html 不会阻塞。
  • 开两个不同浏览器,分别请求请求运行时间较长的 a.html 也不阻塞。只要不用一个浏览去调,它都是不阻塞的;如果开一个浏览器在不同 tab 页请求同一阻塞页面,则会阻塞,这是浏览器引起的。

WSGI 协议

WSGI 是 Web Server Gateway Interface 的缩写,它是 Python 应用程序或者框架(如 Flask)和 web 服务器之间的一种接口。flask 默认使用 werkzeug 库实现 WSGI 协议。

只要实现了 WSGI 协议的任何 web server 都可以作为 flask app 的服务器,比如 uWSGI,Gunicorn,mod_wsgi 都可以替换 Werkzeug 作为 web server。

flask 自带的多进程

  • 在 app.run() 时加入参数:threaded=False, processes=5, debug=False 时,可使用 5 个进程。
  • 进入 flash 的 app.run() 函数内部,可以看到真正使用 werkzeug 库来实现后台服务。
  • flask 自带的多进程有一个问题,每次请求时进程开启,该请求运行结束进程关闭,因此无法在每个进程中保留现场,每次都做初始化,也会浪费很多时间。

gunicorn

  • 如果想在 flash 一开始就启多个进程,可使用 gunicorn
  • 做如下的 test.py
1
2
3
4
5
6
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "Hello World!"

注意:使用 gunicorn 后,无需在程序中运行 app.run()。

  • 运行命令
1
gunicorn -w 3 -b 0.0.0.0:8080 test:app

这里设成开启 3 个进程,0.0.0.0 使得在 docker 内部启动的服务可在宿主机上被访问,test 是 py 文件名,app 是其中的 flask 服务名。此时,使用 ps 命令即可看到一开始就启动了多个进程。

具体应用场景的讨论

flask 框架本身支持多线程和多进程处理,但二者均不适用于输血模型预测。首先,flask 的线程基于 Python 线程,它不是系统级的线程,因此无法充分使用系统资源,经测试此方式使用多线程并不能提升效率;flask 的多进程在请求时新建进程,模型预测需要初始化和加载大量资源,如果不能复用,每次都加载反花费更多时间,因此也无法使用 flask 进程方法。

最终选择使用 gunicorn 方法实现多进程,通过 gunicorn 命令启动服务,启动时设置进程数,该方法在一开始就启动了多个进程,且各进程始终不退出,用以在保留工作环境,同时响应并发处理。

另外,需要权衡内存占用和预测效率。同时开启多个进程将占用大量内存,同时可以处理更多并发请求。经过测试,默认设置为 4 个进程,可通可环境变量设置。并发时,内存可也限制在 2G 以内,且并发预测时间降低为之前的 50% 及以下,同时也兼顾了医院场景中具体的峰值情况。

参考

Flask: flask框架是如何实现非阻塞并发的