原创作者:flowell,转载请标明出处:https://www.cnblogs.com/flowell/p/local_local_proxy_local_stack.html


Local是什么?

无论你接触到的是threading.Local还是werkzeug.Local,它们都代表一种变量——每个线程自己的全局变量。


全局变量,一般位于进程的堆上。一个进程的所有线程都可以访问同一个全局变量,因为它们共享着进程的地址空间,所以每一个线程都可以访问,这也带来了问题,如果多个线程同时访问同一个变量,会对该变量的读写造成不可预估的结果,所以通常我们会使用锁或其他的同步机制来控制线程之间对共享变量的访问。当然了,这不是本文关注的地方。

说回Local,我们在开头提到Local是线程自己的全局变量。所谓线程自己的,就是说该“全局变量”只有拥有的线程自己可以访问,对于其它的线程是不可见的。怎么理解这个定义呢?我们先来看一种场景:函数A处理完参数A,函数B要处理函数A处理过的参数A,那么函数A就要把参数A传递给函数B。如果函数C也要接着处理这个参数呢,函数D也要呢?那么参数A就要在这些函数之间不断地传递,这些函数生命时也要提前声明好参数。可想而知,如果有参数要在函数之间传递,那么函数会变得很复杂,调用函数也很复杂。有没有简便的办法呢?

其实我们在函数间传递参数,为的是要使这个参数对于需要的函数都可视,那么将它变成一个全局变量不就得了?可是变成全局变量的话,其它的线程就会访问到我这个全局变量,可能改变它的值,这不是本线程的本意,我只想一个人独占它。这时,我们就需要一种变量,对于本线程而言,它应该是一个全局变量,对于进程的其它线程而言,它又像是一个局部变量。这就是使用Local的一种场景了,Local就是这样一种变量。

如果在全局域定义了一个Local,那么这个local其实并不是一个全局变量,每个线程访问这个变量时,拿到的实际上都是本线程对应的Local。怎么实现这种效果呢?其实很简单,Local本身并不是一个变量,它还包含了一些操作。你可以这样理解,每个进程都有一个全局的字典,每个线程本身有自己的线程ID,进程的所有线程都可以访问这个全局的字典,那么它们把自己的线程ID当做字典的key,把需要存储的东西当做value,每个线程只能通过自己的key来访问这个字典,那么value本身就相当于一个线程独占的全局变量啦!是不是?每个线程都怪怪地拿属于自己的东西,一个全局的东西,这就相当于一个线程内部的全局变量。具体的代码实现有所区别,但大体上是这个思路。

class Local(object):
__slots__ = ('__storage__', '__ident_func__') def __init__(self):
object.__setattr__(self, '__storage__', {}) # 存放东西的全局字典
object.__setattr__(self, '__ident_func__', get_ident) # 每个线程的key def __iter__(self):
return iter(self.__storage__.items()) def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)    # 这里返回一个LocalProxy对象,LocalProxy是一个代理,代理Local对象。 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):
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)

  

Local怎么用?

伪代码如下

local = Local()
local.request = "i am a request"
local.response = "i am a response" def work():
local.request = xxxx # 每个线程都只会访问到属于自己的request和response
local.response = xxxx # 就算改变response,也只是改变本线程的值 if __name__ == "__main__":
for i in range(10):
Thread(target=work).start()

 

通过声明一个全局的Local对象,然后像访问对象的属性一样访问你要保留的值。你可以这样理解,Local相当于一个字典,我要通过自己定义的key,来访问我需要的值,即调用local.key来获取值。这样使用起来其实很别扭,明明我是定义一个值,却变成像是访问一个对象的属性一样,写起来很奇怪,有时候也不好理解。能不能像定义一个全局变量一样,直接使用一个Local变量呢?

# 我想要这种效果
request = "i am a request"
response = "i am a response"

Local的__call__方法就是干这件事的,使用__call__方法,我们可以让一个Local变得看起来像一个全局变量。

# 你只需要调用Local对象的__call__方法
local = Local()
local.request = "i am a request"
my_request = local("request") # 注意,这里传入的字符串需要和上面保存时的一致
my_request  # "i am a request"

  

my_request现在等同于local.request,比起local.request,my_request是不是看起来更像一个全局变量?但记住,它是一个“线程独有的全局变量”。

LocalProxy是什么?

local相当于一个字典,local.x的x相当于key,而LocalProxy代管了这把key和local,我们只需访问LocaProxy本身,它自动用这把key去local字典查到值,返回给我们,这就是代理(proxy)


my_request实际上是一个LocalProxy,直接访问my_request,它是一个"i am a request"字符串。前面我们提到Local对象可以通过local.xxx=value来存储我需要的本地全局变量,这样的local对象看起来就像一个字典,可以存储任意的值。但是每次都通过local.xxx来获取我们想要的值太麻烦了,我们需要一个对象来帮我们完成这个重复性的动作,把key交给它,把字典交给它,我只要访问它,它就通过key去字典中查值,然后把值返回给我。这样子它对于我来说就像存储的值本身一样。这就是代理。

LocalProxy的原理就是这样,它帮我们干了到Local中查找值的方法,所以我们要把存储local.xxx时的“xxx”这把打开local的key告诉代理,然后把local本身也告诉代理,这样LocalProxy便有了钥匙,和要打开的门,自然他就可以把门里面的东西返回给我们了。从这个角度考虑,Local本身也可以看做是一个代理,它代理的是线程的全局变量,而它持有的key则是线程的id,它会通过id到全局的dict中查找本线程的全局变量,然后返回给我们。

class LocalProxy(object):
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__') def __init__(self, local, name=None):
object.__setattr__(self, '_LocalProxy__local', local)  # 要打开的门
object.__setattr__(self, '__name__', name)         # 钥匙
if callable(local) and not hasattr(local, '__release_local__'):
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, '__wrapped__', local) def _get_current_object(self):
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__)      # 通过key(name)到字典(local)中获取value
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__) @property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
raise AttributeError('__dict__') def __repr__(self):
try:
obj = self._get_current_object()
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
return repr(obj) def __bool__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False def __unicode__(self):
try:
return unicode(self._get_current_object()) # noqa
except RuntimeError:
return repr(self) def __dir__(self):
try:
return dir(self._get_current_object())
except RuntimeError:
return [] def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)  # 通过key(name)到字典(local)中去查找真正的value,并返回 def __setitem__(self, key, value):
self._get_current_object()[key] = value def __delitem__(self, key):
del self._get_current_object()[key] if PY2:
__getslice__ = lambda x, i, j: x._get_current_object()[i:j] def __setslice__(self, i, j, seq):
self._get_current_object()[i:j] = seq def __delslice__(self, i, j):
del self._get_current_object()[i:j] __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)

 LocalProxy中有许多方法,这些方法都是LocalProxy本身实现的一些通用的方法,这些方法不是对本身的调用,而是对代理值的调用。

我们也可以不调用Local的__call__方法构造LocalProxy,可以直接通过LocalProxy的构造函数构造一个LocalProxy,实质上是一样的。

local = Local()
local.request = "request"
my_request = LocalProxy(local, "request") # 第二个参数要和local.xxx的xxx相同

  

LocalStack是什么?

LocalStack和Local差不多,只不过Local像一个字典。LocalStack则是一个栈,存储数据的方式不太一样。可以认为它是一个线程独有的一个全局栈。使用它不用担心被进程的其它线程干扰。

class LocalStack(object):
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和线程安全的区别

Local并不代表着线程安全(Thread-Safe),线程安全更多的是强调多个线程访问同一个全局变量时的同步机制,而Local代表的全局变量时线程独占的,对于其他线程而言是不可见的,所以根本不存在线程安不安全的问题。Local永远只会被本线程操作,所以如果硬是要下一个定义,那么是线程安全的。

Flask解析(一):Local、LocalStak、LocalProxy的更多相关文章

  1. Flask补充--threading.local对象

    目录 Local 局部变量 全局变量 使用threading.local() 自定义threading.local 函数版 面向对象版 通过setattr和getattr实现 每个对象有自己的存储空间 ...

  2. Flask 解析 Web 端 请求 数组

    Web前台由 JavaScript 通过Ajax发送POST请求,当请求数据为数组时,Python Flask 做服务器时的解析如下: js: var ids = []; for (var i = 0 ...

  3. Flask解析(二):Flask-Sqlalchemy与多线程、多进程

    Sqlalchemy flask-sqlalchemy的session是线程安全的,但在多进程环境下,要确保派生子进程时,父进程不存在任何的数据库连接,可以通过调用db.get_engine(app= ...

  4. 六十七:flask上下文之Local线程隔离对象

    Local对象在flask中,类似于request对象,其实是绑定到了werkzeug.local.Local对象上,这样即使是同一个对象,在多线程中都是隔离的,类似的对象还有session以及g对象 ...

  5. Flask之Local、LocalStack和LocalProxy

    在我们使用Flask以及Werkzeug框架的过程中,经常会遇到如下三个概念:Local.LocalStack和LocalProxy.尤其在学习Flask的Request Context和App Co ...

  6. flask基础之LocalProxy代理对象(八)

    前言 flask框架自带的代理对象有四个,分别是request,session,g和current_app,各自的含义我们在前面已经详细分析过.使用代理而不是显式的对象的主要目的在于这四个对象使用太过 ...

  7. Flask源码关于local的实现

    flask源码关于local的实现 try: # 协程 from greenlet import getcurrent as get_ident except ImportError: try: fr ...

  8. flask LOCAL线程隔离技术

    from threading import Thread from werkzeug.local import Local local = Local()#实例化一个线程隔离对象 request = ...

  9. flask 源码专题(十一):LocalStack和Local对象实现栈的管理

    目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于localstack的实现 3. 总结 04 LocalS ...

随机推荐

  1. redis-公平信号量

    但各个系统的系统时间并不完全相同时,基本信号量就会出现问题:系统时间较慢的系统,将能够偷走系统时钟快的系统的信号量,导致信号量变得不公平.以下方法,只要系统间时间相差不到1秒,就不会出现信号量被偷或提 ...

  2. mybatis简单入门介绍

    mybatis入门 简介 什么是mybatis? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及 ...

  3. Marshmallow权限使用

    Google发布Android 6.0后对用权限的控制更加严格,在Android5.1或更低的版本中用户能在App的安装期间或使用设置应用程序权限来同意或拒绝某个权限,而在Android6.0或更高的 ...

  4. ArchLinux安(重)装指南

    说实话,我其实是不想要出这篇博客的.在我这一个月安装Arch的过程中,让我感触比较深的一点是: 没有谁比这个系统的官方更懂它. 尤其是这种比较复杂的系统,更是如此. 这几天,我经历了一次重装,系统坏了 ...

  5. HDU 2044——一只小蜜蜂...(DP)

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=2044 题解 //递归思想,超时 #include<iostream> using namesp ...

  6. Python爬虫:获取JS动态内容

    经过一段时间的python学习,能写出一些爬虫了.但是,遇到js动态加载的网页就犯了难.于是乎谷歌.百度,发现个好介绍http://www.jianshu.com/p/4fe8bb1ea984 主要就 ...

  7. asp.net 开源工作流-ccflow关于 “ 是否自动计算未来的处理人”的功能变更

    关键字:流程未来节点处理人  工作流快速开发平台  工作流流设计  业务流程管理   asp.net 开源工作流 业务背景:一个流程在启动起来后,是可以对一些节点计算出来处理人是谁,流程的走向.对于另 ...

  8. 终端-Linux命令之非交互SSH密码验证-Sshpass

    Sshpass是使用SSH所谓的"交互式键盘密码身份验证"以非交互方式执行密码身份验证的工具 通俗来说就是 使用ssh密码登录 是需要在连接时手动输入密码的,没办法明文连接,如下图 ...

  9. 学Python的第一天

    第一天学习笔记 一.安装typroa 下载软件typroa用于日常学习笔记记录,该软件支持markdown语法 步骤: 官网地址:https://typora.io/ 选择版本安装(以windows为 ...

  10. java中的Math.ceil、Math.floor和Math.round

    ceil意为天花板,指向上取整:floor意为地板,指向下取整:round指四舍五入 package com.company; public class Main { public static vo ...