问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

什么是 ThreadLocal 类?ThreadLocal 是一个线程级别的局部变量如何理解?

发布网友 发布时间:2022-04-18 09:02

我来回答

2个回答

懂视网 时间:2022-04-18 13:24

Werkzeug 作为一个 WSGI 工具库,由于一些方面的考虑,并没有直接使用python内置的ThreadLocal类,而是自己实现了一系列Local类。包括简单的Local,以及在此基础上实现的LocalStack,LocalManager 和 LocalProxy。接下来我们一起来看看这些类的使用方式,设计的初衷,以及具体的实现技巧。

Local 类的设计

Werkzeug 的设计者认为python自带的ThreadLocal并不能满足需求,主要因为下面两个原因:

Werkzeug 主要用“ThreadLocal”来满足并发的要求,python 自带的ThreadLocal只能实现基于线程的并发。而python中还有其他许多并发方式,比如常见的协程(greenlet),因此需要实现一种能够支持协程的Local对象。

WSGI不保证每次都会产生一个新的线程来处理请求,也就是说线程是可以复用的(可以维护一个线程池来处理请求)。这样如果werkzeug 使用python自带的ThreadLocal,一个“不干净(存有之前处理过的请求的相关数据)”的线程会被用来处理新的请求。

为了解决这两个问题,werkzeug 中实现了Local类。Local对象可以做到线程和协程之间数据的隔离,此外,还要支持清理某个线程或者协程下的数据(这样就可以在处理一个请求之后,清理相应的数据,然后等待下一个请求的到来)。

具体怎么实现的呢,思想其实特别简单,我们在深入理解Python中的ThreadLocal变量(上)一文的最后有提起过,就是创建一个全局字典,然后将线程(或者协程)标识符作为key,相应线程(或协程)的局部数据作为 value。这里 werkzeug 就是按照上面思路进行实现,不过利用了python的一些黑魔法,最后提供给用户一个清晰、简单的接口。

具体实现

Local 类的实现在 werkzeug.local 中,以 8a84b62 版本的代码进行分析。通过前两篇对ThreadLocal的了解,我们已经知道了Local对象的特点和使用方法。所以这里不再给出Local对象的使用例子,我们直接看代码。

class Local(object): 
 __slots__ = ('__storage__', '__ident_func__') 
 
 def __init__(self): 
 object.__setattr__(self, '__storage__', {}) 
 object.__setattr__(self, '__ident_func__', get_ident) 
 ...

由于可能有大量的Local对象,为了节省Local对象占用的空间,这里使用 __slots__ 写死了Local可以拥有的属性:

__storage__: 值为一个字典,用来保存实际的数据,初始化为空;

__ident_func__:值为一个函数,用来找到当前线程或者协程的标志符。

由于Local对象实际的数据保存在__storage__中,所以对Local属性的操作其实是对__storage__的操作。对于获取属性而言,这里用魔术方法__getattr__拦截__storage__ 和 __ident_func__以外的属性获取,将其导向__storage__存储的当前线程或协程的数据。而对于属性值的set或者del,则分别用__setattr__和__setattr__来实现(这些魔术方法的介绍见属性控制)。关键代码如下所示:

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)

假设我们有ID为1,2, ... , N 的N个线程或者协程,每个都用Local对象保存有自己的一些局部数据,那么Local对象的内容如下图所示:

892670031-581ee02140c15_articlex.png

此外,Local类还提供了__release_local__方法,用来释放当前线程或者协程保存的数据。

Local 扩展接口

Werkzeug 在 Local 的基础上实现了 LocalStack 和 LocalManager,用来提供更加友好的接口支持。

LocalStack

LocalStack通过封装Local从而实现了一个线程(或者协程)独立的栈结构,注释里面有具体的使用方法,一个简单的使用例子如下

ls = LocalStack() 
ls.push(12) 
print ls.top # 12 
print ls._local.__storage__ 
# {140735190843392: {'stack': [12]}}

LocalStack 的实现比较有意思,它将一个Local对象作为自己的属性_local,然后定义接口push, pop 和 top 方法进行相应的栈操作。这里用 _local.__storage__._local.__ident_func__() 这个list来模拟栈结构。在接口push, pop和top中,通过操作这个list来模拟栈的操作,需要注意的是在接口函数内部获取这个list时,不用像上面黑体那么复杂,可以直接用_local的getattr()方法即可。以 push 函数为例,实现如下:

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

pop 和 top 的实现和一般栈类似,都是对 stack = getattr(self._local, 'stack', None) 这个列表进行相应的操作。此外,LocalStack还允许我们自定义__ident_func__,这里用 内置函数 property 生成了描述器,封装了__ident_func__的get和set操作,提供了一个属性值__ident_func__作为接口,具体代码如下:

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__

LocalManager

Local 和 LocalStack 都是线程或者协程独立的单个对象,很多时候我们需要一个线程或者协程独立的容器,来组织多个Local或者LocalStack对象(就像我们用一个list来组织多个int或者string类型一样)。

Werkzeug实现了LocalManager,它通过一个list类型的属性locals来存储所管理的Local或者LocalStack对象,还提供cleanup方法来释放所有的Local对象。Werkzeug中LocalManager最主要的接口就是装饰器方法make_middleware,代码如下:

def make_middleware(self, app): 
 """Wrap a WSGI application so that cleaning up happens after 
 request end. 
 """ 
 def application(environ, start_response): 
 return ClosingIterator(app(environ, start_response), self.cleanup) 
 return application

这个装饰器注册了回调函数cleanup,当一个线程(或者协程)处理完请求之后,就会调用cleanup清理它所管理的Local或者LocalStack 对象(ClosingIterator 的实现在 werkzeug.wsgi中)。下面是一个使用 LocalManager 的简单例子:

from werkzeug.local import Local, LocalManager 
 
local = Local() 
local_2 = Local() 
local_manager = LocalManager([local, local2]) 
 
def application(environ, start_response): 
 local.request = request = Request(environ) 
 ... 
 
# application 处理完毕后,会自动清理local_manager 的内容

通过LocalManager的make_middleware我们可以在某个线程(协程)处理完一个请求后,清空所有的Local或者LocalStack对象,这样这个线程又可以处理另一个请求了。至此,文章开始时提到的第二个问题就可以解决了。Werkzeug.local 里面还实现了一个 LocalProxy 用来作为Local对象的代理,也非常值得去学习。

热心网友 时间:2022-04-18 10:32

ThreadLocal变量就是和线程绑定的变量.实际上是一个Map,,,key是对应的线程,值则是该变量.
调用ThreadLocal的get方法时则会到Map中查询当前线程是否已拥有该变量,如果没有则新建一个并保存到Map中.有的话直接返回与该线程绑定的变量.
说白了就是每个线程拥有不同的实例.以空间换时间.追问ThreadLocal不是一个类么为什么书上说他是一个线程级别的变量?我就不理解他怎么成了变量了?请大神帮忙解释一下,谢啦

追答你可以看看JDK的帮助文档,上面有一些对Threadlocal的介绍.
ThreadLocal的确是一个类,它的实例可以称为线程级别的变量.
ThreadLocal的实例往往设置为static类型,这样ThreadLocal的实例实质上就变成类的实例.
比如一个类MyThreadLocalVar,,,其中有一个public static的ThreadLocal实例myVar.
在多线程环境中,Thread1通过MyThreadLocalVar.myVar.get()方式获取myVar的值.这个值是与Thread1关联的,,,,Thread2也可以通过MyThreadLocalVar.myVar.get()方式获取myVar的值,这个值则与Thread2关联,,,这两个myVar.get()的值(或者对象)是类的不同实例.彼此互不干扰.

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
怎样才能不打开QQ聊天窗口就能在右下角显示别人发来的消息? ...他不是你好友,但在网页上点一个东西,就弹出跟他QQ聊天的窗口... 一打开QQ聊天窗口,就自动发一幅图片 绿萝卜干怎么拌好吃 萝卜干怎么做好吃今天试试凉拌 拌萝卜干咸菜的做法 家里晒的萝卜干需要怎么处理才能做成凉拌菜? 拉普兰德童话故事》 一年级的孩子买读书郎学习电脑好还是点读机好呢?其他品牌的怎么样... 读书郎点读机真的对孩子学习有帮助吗?4岁孩子急于要是否给买呢? 1990年属马几月出生好女孩,属马女哪个月最有福气 微信收款商业版延迟结算无需处理怎么办? 新浪微博需要加V认证,但是要两个橙V互粉 新浪微博认证需要两个橙V,求帮助 新浪微博申请认证,需要两个加v还差一个 新浪微博认证必须要两个加V好友吗?怎么加,谁帮帮忙啊! 记得世界环境日是否能代表一个人有环保意识? 为什么我的电脑显示器开机总是闪还不显示桌面 中国野生动物保护协会的会徽上画的是什么动物? 开机后无法进入桌面只有一直在闪的横线? 电脑开机,进入桌面,一直开始闪。怎么办? DIY自己的老电脑,主板不动的情况下,想让性能更好点,应该怎么升级? 如何升级主板BIOS 电脑主板BIOS如何升级? 如何升级我的电脑主板? 头特别痒是什么原因 如何实现打印机从第5页打到第10页? pdf 打印 共20页的文档,我只要打印第5-10页怎么设置呢?请大家指教啊~ 打印表格共7页,我只想要第5页该怎么操作? 在word2010中,某文档共20页,现需要将其第3页到第10页打印5份,该如何操作 word文档有20页,只想打印其中的第5至10页,如何操作? 汽车发动后车载充电器不能充电,但是熄火后又可以充电是怎么回事呢? 学校宽带用不了路由器,会被冻结账号,怎么破。基友们 业余爱好买数字电桥哪款好 数字电桥国产哪个品牌好 麦聚瑞? 学校宽带能用路由器吗 哪个牌子的高压电桥比较好用啊?求推荐。 手持数字电桥哪家好 为什么我们学校只能用交换机而不能用路由器? 国产1M电桥那个牌子好? 学校的宽带网线不能用路由器共享,怎么办?恳请高手指点!! 哪种数字电桥好点? 哪种型号的电桥比较好?精密度要高点的,国产的··不要便宜那种····几千块钱的就别回答了,我们公司 公司最近要购置一批高压电缆检测设备,哪个品牌好? 安捷伦电桥与通惠电桥哪个好 ThreadLocal如何保证获取到想要线程变量 百度carl_fe怎么播放视频 男生如何使胳膊变粗 家里没器材男生怎么锻炼手臂.让手臂变粗. 我是男生 我的胳膊很细!怎么练才能变粗些??? 苏格拉底的我知道我一无所知什么意思