Flask 上下文机制和线程隔离
1. 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决, 上下文机制就是这句话的体现。
2. 如果一次封装解决不了问题,那就再来一次
上下文:相当于一个容器,保存了Flask程序运行过程中的一些信息 源码:flask/ctx.py
请求上下文:Flask从客户端收到请求时,要让视图函数能访问一些对象,这样才能处理请求,要想让视图函数能够访问请求对象,一个显而易见的方式是将其作为参数传入视图函数,不过这会导致程序中的每个视图函数都增加一个参数,除了访问请求对象,如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。为了避免大量可有可无的参数把视图函数弄得一团糟,Flask使用上下文临时把某些对象变为全局可访问。这就是一种重构设计思路。
- request 封装了HTTP请求的内容,针对http请求,也是一种符合WSGI接口规范的设计(关于WSGI可参考我对该协议的理解和实现demo mini-wsgi-web),如 
request.args.get('user') - session 用来记录请求会话中的信息,针对的是用户信息,如 
session['name'] = user.id 
- request 封装了HTTP请求的内容,针对http请求,也是一种符合WSGI接口规范的设计(关于WSGI可参考我对该协议的理解和实现demo mini-wsgi-web),如 
 应用上下文:应用程序上下文,用于存储应用程序中的变量
- current_app 存储应用配置,数据库连接等应用相关信息
 - g变量 作为flask程序全局的一个临时变量, 充当者中间媒介的作用,我们可以通过它传递一些数据,g保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
 
# context locals
# 使用代理模式 LocalProxy
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
1. working outside application context
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# __author__ = '__JonPan__'
from flask import Flask, current_app
app = Flask(__name__)
a = current_app
# is_debug = current_app.config['DEBUG']
@app.route('/')
def index():
    return '<h1>Hello World. Have a nice day! </1>'
if __name__ == '__main__':
    app.run(host='localhost', port=8888)
报错:
Exception has occurred: RuntimeError
Working outside of application context.
2. flask 上下文出入栈
flask上下文对象出入栈模型图

在应用开发中可用直接引用current_app不会报错,是因为当在一个请求中使用的时候,flask会判断_app_ctx_stack栈顶是否有可用对象,如果没有就会自动推入一个App. 我们获取的current_app就是获取的栈顶元素
# flask/globals.py
def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app
current_app = LocalProxy(_find_app)
修改代码:将app对象推入栈顶
# ...
# 将app_context 推入栈中
ctx = app.app_context()
ctx.push()
a = current_app
is_debug = current_app.config['DEBUG']
ctx.pop()
# ...
# 更有pythonnic的写法
# 将app_context 推入栈中
# with app.app_context():
#     a = current_app
#     is_debug = current_app.config['DEBUG']

不在出现unbound状态。可正常运行
既然flask会自动帮我们检测栈顶元素是否存在,为什么我们还要做这一步操作,当我们在写离线应用,或者单元测试的时候就需要用到,因为请求是模拟的,不是在application context中的了。
3. python中的上文管理器
实现了__enter__和__exit__方法的对象就是一个上文管理器。
class MyResource:
    def __enter__(self):
        print('connect ro resource')
        return self
    def __exit__(self, exc_type, exc_value, tb):
        if tb:
            print('process exception')
        else:
            print('no exception')
        print('close resource connection')
        # return True
        # return False
    def query(self):
        print('query data')
try:
    with MyResource() as r:
        1/0
        r.query()
except Exception as e:
    print(e)
with MyResour ce() as r as 的别名r指向的不是上想问管理器对象,而是__enter__方法返回的值,在以上代码确实是返回了对象本身。
__exit__ 方法 处理退出上下文管理器对象时的一些资源清理工作,并处理异常,三个参数
- exc_type 异常类型
 - exc_value 异常原因解释
 - tb traceback
 
__exit__其实是有返回值的,return True表示,异常信息已经在本方法中处理,外部可不接收异常,return False 表示将异常抛出给上层逻辑处理,默认不写返回,即默认值是None,  None也表示False
线程隔离机制

如何实现一个Reqeust 指向多个请求实例,且要区分该实例对象所绑定的用户?
字典:
request = {'key1': val1, 'key2': val2}
flask引用 werkzeug 中的 local.Local 实现线程隔离
# werkzeug\local.py
class Local(object):
    __slots__ = ('__storage__', '__ident_func__')
    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)
    def __iter__(self):
        return iter(self.__storage__.items())
    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)
    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)
    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
    def __setattr__(self, name, value):
        # 获取当前线程的id
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}
    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
可以看到是使用线程ID来绑定不同的上线文对象。
LocalStack
class LocalStack(object):
    """This class works similar to a :class:`Local` but keeps a stack
    of objects instead.  This is best explained with an example::
        >>> ls = LocalStack()
        >>> ls.push(42)
        >>> ls.top
        42
        >>> ls.push(23)
        >>> ls.top
        23
        >>> ls.pop()
        23
        >>> ls.top
        42
    They can be force released by using a :class:`LocalManager` or with
    the :func:`release_local` function but the correct way is to pop the
    item from the stack after using.  When the stack is empty it will
    no longer be bound to the current context (and as such released).
    By calling the stack without arguments it returns a proxy that resolves to
    the topmost item on the stack.
    .. versionadded:: 0.6.1
    """
    def __init__(self):
        self._local = Local()
    def __release_local__(self):
        self._local.__release_local__()
    def _get__ident_func__(self):
        return self._local.__ident_func__
    def _set__ident_func__(self, value):
        object.__setattr__(self._local, '__ident_func__', value)
    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__
    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
            return rv
        return LocalProxy(_lookup)
    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv
    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()
    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None
Local使用字典的方式实现线程隔离,LocalStack 则封装Local实现了线程隔离的栈结构
总结
- flask上下文的实现使用了设计模式中的代理模式
 - 线程隔离的实现机制就是利用线程ID+字典, 使用线程隔离的意义在于:使当前线程能够正确引用到他自己所创建的对象,而不是引用到其他线程所创建的对象
 - 请求上下文, 应用上线文是线程隔离的对象,current_app并没有使用线程隔离,全局唯一
 current_app -> (LocalStack.top = AppContext top.app = Flask)request -> (LocalStack.top = RequestContext.top.request = Request)
参考资料
Flask 上下文机制和线程隔离的更多相关文章
- 六十七:flask上下文之Local线程隔离对象
		
Local对象在flask中,类似于request对象,其实是绑定到了werkzeug.local.Local对象上,这样即使是同一个对象,在多线程中都是隔离的,类似的对象还有session以及g对象 ...
 - flask高级编程  LocalStack 线程隔离
		
转:https://www.cnblogs.com/wangmingtao/p/9372611.html 30.LocalStack作为线程隔离对象的意义 30.1 数据结构 限制了某些能力 30 ...
 - 4.1 python类的特殊成员,偏函数,线程安全,栈,flask上下文
		
目录 一. Python 类的特殊成员(部分) 二. Python偏函数 1. 描述 2. 实例一: 取余函数 3. 实例二: 求三个数的和 三. 线程安全 1. 实例一: 无线程,消耗时间过长 2. ...
 - 详解Flask上下文
		
上下文是在Flask开发中的一个核心概念,本文将通过阅读源码分享下其原理和实现. Flask系列文章: Flask开发初探 WSGI到底是什么 Flask源码分析一:服务启动 Flask路由内部实现原 ...
 - 六十九:flask上下文之线程隔离的g对象的使用
		
保存全局对象的g对象g对象是在整个flask应用运行期间都是可以使用的,并且也是和request一样,是线程隔离的,这个对象是专门用来存放开发者自己定义的一些数据,方便在整个flask程序中都可以使用 ...
 - Flask核心机制--上下文源码剖析
		
一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...
 - Flask上下文管理机制
		
前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...
 - flask LOCAL线程隔离技术
		
from threading import Thread from werkzeug.local import Local local = Local()#实例化一个线程隔离对象 request = ...
 - 使用Hystrix的插件机制,解决在使用线程隔离时,threadlocal的传递问题
		
背景 在我们的项目中,比较广泛地使用了ThreadLocal,比如,在filter层,根据token,取到用户信息后,就会放到一个ThreadLocal变量中:在后续的业务处理中,就会直接从当前线程, ...
 
随机推荐
- Linux: ssh命令 远程登录
			
1.查看SSH客户端版本 使用ssh -V命令可以得到版本号.需要注意的是,Linux一般自带的是OpenSSH; $ ssh -V ssh: SSH Secure Shell 3.2.9.1 (no ...
 - 2019-02-15  CSS样式学习——(内容来自mooc北京林业大学web前端开发授课ppt)
			
CSS样式的添加方法 行内样式 e.x. 内嵌样式 e.x. 注意内嵌样式写在head标签内: 单独文件(外部式样式表文件) 也是在head标签内插入超链接,sublime+emmet可以用link: ...
 - (五)pom文件详解
			
<?xml version="1.0" encoding="UTF-8"?> <!--是所有pom.xml的根元素,并且在里面定义了命名空间和 ...
 - 机器学习中的标准化方法(Normalization Methods)
			
希望这篇随笔能够从一个实用化的角度对ML中的标准化方法进行一个描述.即便是了解了标准化方法的意义,最终的最终还是要:拿来主义,能够在实践中使用. 动机:标准化的意义是什么? 我们为什么要标准化?想象我 ...
 - APP自动化1——Appium+pycharm自动化环境搭建全流程
			
1. 安装python3,pycharm,可参考之前写的文档:https://www.cnblogs.com/chenweitoag/p/13154815.html 2. 准备以下必要工具: 基于wi ...
 - MySQL的使用方法和视图、索引、以及存储过程的一些简单方法
			
一,基本概念 1, 常用的两种引擎: (1) InnoDB a,支持ACID,简单地说就是支持事务完整性.一致性: b,支持行锁,以及类似ORACLE的一 ...
 - EM(最大期望)算法推导、GMM的应用与代码实现
			
EM算法是一种迭代算法,用于含有隐变量的概率模型参数的极大似然估计. 使用EM算法的原因 首先举李航老师<统计学习方法>中的例子来说明为什么要用EM算法估计含有隐变量的概率模型参数. 假设 ...
 - VMware Workstation 15密钥
			
在打开的VMware Workstation 15输入许可证密钥对话框里直接输入25位密钥,然后点击确定,如下图所示. 这里提供一个密钥: CG392-4PX5J-H816Z-HYZNG-PQRG2
 - .NETCore微服务探寻(二) - 认证与授权
			
前言 一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目 ...
 - 第十章:RDB持久化
			
RDB 保存命令 save 命令,阻塞 Redis 服务器进程,直到保存动作完成: bgsave 命令,派生出一个子进程来完成保存动作: 载入命令 Redis 没有载入 RDB 文件的命令,载入动作在 ...