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

如何实现一个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中.有的话直接返回与该线程绑定的变量.说白了就是每个线程拥有不同的实例.以空间换时间.
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
立秋后钓鱼什么风向好-立秋钓鱼风向怎么选 赱乂砉込儬 怎么读 "赱"读什么? 解决电脑无法搜索到iPhone热点的问题如何修复iPhone热点无法在电脑上显... 电脑搜不到iphone12热点 iphone开热点给电脑搜不到 国企的地产有哪些 国企下设公司是什么意思? 红加蓝加绿是什么色. 梦幻西游75级开三倍抓鬼一轮有多少经验 松下等离子电视有城乡锅盖机顶盒可以收看电视节目吗? oppo a520支持图片分辨率 OPPO A520内存支持多大的扩展 OPPOa520,手机支持多大储存卡 乐星松下电视机怎样自动搜台,自动搜台英文字母是什么 OPPOa520和a203哪个好点? oppoA520手机怎么样? oppoA520像素是多少? oppoa520和朵唯S630 oppoa520介绍 OPPOA520性能怎么样? OPPOA520手机详情 OPPO A520手机的参数(视频解码器、视频帧率、视频比特率、音频解码器、音频比特率)是多少? 网上有电工教学视频吗, 0基础学电工可以吗,电工基础视频教程,电工基础知识 求初级电工教学视频和三相异步电动机的拆装 视屏 有谁知道哪个网址播放电工的视频,从零开始学,我不会接电,想通过视频学习。 京瓷5035复印机粉盒齿轮转不动 京瓷2035复印机粉盒是否可加粉?大神们帮帮忙 京瓷181复印机刚换新的磨粉盒为什么复印的都是全黑的 现在读绿然不好进了是吗 四川省绿然现代农业科技有限责任公司怎么样? 四川绿然新能源有限责任公司怎么样? 成都绿然企业管理有限公司怎么样? 四川绿冶科技有限责任公司怎么样? 本溪绿然生物科技有限公司怎么样? 武汉绿然中药饮片有限公司怎么样? 四川力伦科技有限公司怎么样? 长沙绿然生物科技有限公司怎么样? 四川生工科技有限公司怎么样? 股票,除权除息日那天, 每十股派现金5.7元,但是派到哪里去了,我在账户上没找到喔,请各位帮忙解答。谢 10派3元后为什么股价反而下跌呢?这些钱派了之后到哪里去了?怎么都... 不是说每10股派现0.35元吗,请问派到股东哪儿,我怎么知道有没有收到? ThreadLocal变量与一般成员变量有什么区别 王者荣耀游走段位怎么升级 王者荣耀游走段位怎么提高 吃完杏剩下的杏核里的仁能吃吗?? 杏里面的核剥出的仁能吃吗 请问杏子的仁能吃么? 为什么夏天穿毛呢大衣服不舒服选自?