摘要:在我之前写的中源码的深究和理解一文中解释了如何支持多线程主要通过两个类来实现和在中有两个属性和后者用来获取线程从而区分不同线程发来的请求这次要说的是如何开启多线程先从这个方法看起会进入这个函数经过判断和设置后进入这个函数看下源码
在我之前写的《flask中current_app、g、request、session源码的深究和理解》一文中解释了flask如何支持多线程
主要通过两个类来实现,LocalStack和Local,在Local中有两个属性,__storage__和__ident_func__,后者用来获取线程id,从而区分不同线程发来的请求
这次要说的是flask如何开启多线程
先从app.run()这个方法看起
def run(self, host=None, port=None, debug=None, **options): from werkzeug.serving import run_simple if host is None: host = "127.0.0.1" if port is None: server_name = self.config["SERVER_NAME"] if server_name and ":" in server_name: port = int(server_name.rsplit(":", 1)[1]) else: port = 5000 if debug is not None: self.debug = bool(debug) options.setdefault("use_reloader", self.debug) options.setdefault("use_debugger", self.debug) try: run_simple(host, port, self, **options) #会进入这个函数 finally: # reset the first request information if the development server # reset normally. This makes it possible to restart the server # without reloader and that stuff from an interactive shell. self._got_first_request = False
经过判断和设置后进入run_simple()这个函数,看下源码
def run_simple(hostname, port, application, use_reloader=False,
use_debugger=False, use_evalex=True, extra_files=None, reloader_interval=1, reloader_type="auto", threaded=False, processes=1, request_handler=None, static_files=None, passthrough_errors=False, ssl_context=None): """Start a WSGI application. Optional features include a reloader, multithreading and fork support. This function has a command-line interface too:: python -m werkzeug.serving --help .. versionadded:: 0.5 `static_files` was added to simplify serving of static files as well as `passthrough_errors`. .. versionadded:: 0.6 support for SSL was added. .. versionadded:: 0.8 Added support for automatically loading a SSL context from certificate file and private key. .. versionadded:: 0.9 Added command-line interface. .. versionadded:: 0.10 Improved the reloader and added support for changing the backend through the `reloader_type` parameter. See :ref:`reloader` for more information. :param hostname: The host for the application. eg: ``"localhost"`` :param port: The port for the server. eg: ``8080`` :param application: the WSGI application to execute :param use_reloader: should the server automatically restart the python process if modules were changed? :param use_debugger: should the werkzeug debugging system be used? :param use_evalex: should the exception evaluation feature be enabled? :param extra_files: a list of files the reloader should watch additionally to the modules. For example configuration files. :param reloader_interval: the interval for the reloader in seconds. :param reloader_type: the type of reloader to use. The default is auto detection. Valid values are ``"stat"`` and ``"watchdog"``. See :ref:`reloader` for more information. :param threaded: should the process handle each request in a separate thread? :param processes: if greater than 1 then handle each request in a new process up to this maximum number of concurrent processes. :param request_handler: optional parameter that can be used to replace the default one. You can use this to replace it with a different :class:`~BaseHTTPServer.BaseHTTPRequestHandler` subclass. :param static_files: a list or dict of paths for static files. This works exactly like :class:`SharedDataMiddleware`, it"s actually just wrapping the application in that middleware before serving. :param passthrough_errors: set this to `True` to disable the error catching. This means that the server will die on errors but it can be useful to hook debuggers in (pdb etc.) :param ssl_context: an SSL context for the connection. Either an :class:`ssl.SSLContext`, a tuple in the form ``(cert_file, pkey_file)``, the string ``"adhoc"`` if the server should automatically create one, or ``None`` to disable SSL (which is the default). """ if not isinstance(port, int): raise TypeError("port must be an integer") if use_debugger: from werkzeug.debug import DebuggedApplication application = DebuggedApplication(application, use_evalex) if static_files: from werkzeug.wsgi import SharedDataMiddleware application = SharedDataMiddleware(application, static_files) def log_startup(sock): display_hostname = hostname not in ("", "*") and hostname or "localhost" if ":" in display_hostname: display_hostname = "[%s]" % display_hostname quit_msg = "(Press CTRL+C to quit)" port = sock.getsockname()[1] _log("info", " * Running on %s://%s:%d/ %s", ssl_context is None and "http" or "https", display_hostname, port, quit_msg) def inner(): try: fd = int(os.environ["WERKZEUG_SERVER_FD"]) except (LookupError, ValueError): fd = None srv = make_server(hostname, port, application, threaded, processes, request_handler, passthrough_errors, ssl_context, fd=fd) if fd is None: log_startup(srv.socket) srv.serve_forever() if use_reloader: # If we"re not running already in the subprocess that is the # reloader we want to open up a socket early to make sure the # port is actually available. if os.environ.get("WERKZEUG_RUN_MAIN") != "true": if port == 0 and not can_open_by_fd: raise ValueError("Cannot bind to a random port with enabled " "reloader if the Python interpreter does " "not support socket opening by fd.") # Create and destroy a socket so that any exceptions are # raised before we spawn a separate Python interpreter and # lose this ability. address_family = select_ip_version(hostname, port) s = socket.socket(address_family, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(get_sockaddr(hostname, port, address_family)) if hasattr(s, "set_inheritable"): s.set_inheritable(True) # If we can open the socket by file descriptor, then we can just # reuse this one and our socket will survive the restarts. if can_open_by_fd: os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno()) s.listen(LISTEN_QUEUE) log_startup(s) else: s.close() # Do not use relative imports, otherwise "python -m werkzeug.serving" # breaks. from werkzeug._reloader import run_with_reloader run_with_reloader(inner, extra_files, reloader_interval, reloader_type) else: inner() #默认会执行
还是经过一系列判断后默认会进入inner()函数,这个函数定义在run_simple()内,属于闭包,inner()中会执行make_server()这个函数,看下源码:
def make_server(host=None, port=None, app=None, threaded=False, processes=1,
request_handler=None, passthrough_errors=False, ssl_context=None, fd=None): """Create a new server instance that is either threaded, or forks or just processes one request after another. """ if threaded and processes > 1: raise ValueError("cannot have a multithreaded and " "multi process server.") elif threaded: return ThreadedWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd) elif processes > 1: return ForkingWSGIServer(host, port, app, processes, request_handler, passthrough_errors, ssl_context, fd=fd) else: return BaseWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd)
看到这也很明白了,想要配置多线程或者多进程,则需要设置threaded或processes这两个参数,而这两个参数是从app.run()中传递过来的:
app.run(**options) ---> run_simple(threaded,processes) ---> make_server(threaded,processes)
默认情况下flask是单线程,单进程的,想要开启只需要在run中传入对应的参数:app.run(threaded=True)即可.
从make_server中可知,flask提供了三种server:ThreadedWSGIServer,ForkingWSGIServer,BaseWSGIServer,默认情况下是BaseWSGIServer
以线程为例,看下ThreadedWSGIServer这个类:
class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer): #继承自ThreadingMixIn, BaseWSGIServer
"""A WSGI server that does threading.""" multithread = True daemon_threads = True
ThreadingMixIn = socketserver.ThreadingMixIn
class ThreadingMixIn:
"""Mix-in class to handle each request in a new thread.""" # Decides how threads will act upon termination of the # main process daemon_threads = False def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. """ try: self.finish_request(request, client_address) self.shutdown_request(request) except: self.handle_error(request, client_address) self.shutdown_request(request) def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) t.daemon = self.daemon_threads t.start()
process_request就是对每个请求产生一个新的线程来处理
最后写一个非常简单的应用来验证以上说法:
from flask import Flask
from flask import _request_ctx_stack
app = Flask(__name__)
@app.route("/")
def index():
print(_request_ctx_stack._local.__ident_func__()) while True: pass return "hello
"
app.run() #如果需要开启多线程则app.run(threaded=True)
_request_ctx_stack._local.__ident_func__()对应这get_ident()这个函数,返回当前线程id,为什么要在后面加上while True这句呢,我们看下get_ident()这个函数的说明:
Return a non-zero integer that uniquely identifies the current thread amongst other threads that exist simultaneously. This may be used to identify per-thread resources. Even though on some platforms threads identities may appear to be allocated consecutive numbers starting at 1, this behavior should not be relied upon, and the number should be seen purely as a magic cookie. A thread"s identity may be reused for another thread after it exits.
关键字我已经加粗了,线程id会在线程结束后重复利用,所以我在路由函数中加了这个死循环来阻塞请求以便于观察到不同的id,这就会产生两种情况:
1.没开启多线程的情况下,一次请求过来,服务器直接阻塞,并且之后的其他请求也都阻塞
2.开启多线程情况下,每次都会打印出不同的线程id
第一种情况
Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
139623180527360
第二种情况
Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
140315469436672
140315477829376
140315486222080
140315316901632
140315105163008
140315096770304
140315088377600
结果显而易见
综上所述:flask支持多线程,但默认没开启,其次app.run()只适用于开发环境,生产环境下可以使用uWSGI,Gunicorn等web服务器
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/44732.html
摘要:示例如下静态路由使用动态变量的路由未指定变量类型使用动态变量的路由指定变量类型指定的路由变量,可以作为被装饰的函数参数传入进来。 开始决定认真的在网上写一些东西,主要原因还是在于希望能提升学习效果。虽说python写了有几年,但是web后端框架的确没怎么接触过,买了本狗书寥寥草草的过了一遍,发现很多东西还是理解不深,真的是好记性不如烂笔头,知识也要从基础开始,退回来好好看看官方文档,再...
摘要:前面两篇讲明了怎么支持多线程以及怎么开启多线程的这篇来讲讲当后端接收到请求后是怎么一步步封装的类中的当应用启动后会通过接收请求中返回的是方法主要做了两件事情第一件事是通过的另一个方法返回得到了一个封装好的对象然后调用中的在最后调用了将请求对 前面两篇讲明了flask怎么支持多线程以及怎么开启多线程的,这篇来讲讲当后端接收到请求后是怎么一步步封装的 Flask类中的wsgi_app()当...
摘要:函数携带目的地址主题邮件体模板和一组关键字参数。许多扩展操作是在假设有活动的应用程序和请求上下文的情况下进行的。但是当函数在一个不同的线程上执行,应用程序上下文需要人为地创建使用。例如,执行函数可以将邮件发送到的任务队列中。 许多类型的应用程序都会在某些事件发生的时候通知用户,常用的沟通方法就是电子邮件。尽管在Flask应用程序中,可以使用Python标准库中的smtplib包来发送电...
摘要:从存储的字符串表示中检索原始对象的过程称为。这称为命名空间。如果需要八进制或十六进制表示,请使用内置函数或。和有什么区别返回对象,而返回列表,并使用相同的内存,无论范围大小是多少。它提供了灵活性,并允许开发人员为他们的项目使用正确的工具。 ...
摘要:有两类应用级和请求级。一个响应中非常重要的部分是状态码,默认设置来指示请求已经成功处理。重定向通常由响应状态码注明并且重定向的由头部的给出。因为这些变化,应用程序获得一组基本的命令行选项。运行显示可用信息在应用程序上下文的内部运行一个。 5、请求-响应循环 现在你已经玩过一个基本的Flask应用程序,你也许想要知道更多关于Flask如何施展魔力。下面章节描述了一些框架设计方面的特点。...
阅读 2882·2021-11-22 09:34
阅读 1212·2021-11-19 09:40
阅读 3335·2021-10-14 09:43
阅读 3566·2021-09-23 11:22
阅读 1601·2021-08-31 09:39
阅读 880·2019-08-30 15:55
阅读 1414·2019-08-30 15:54
阅读 854·2019-08-30 15:53