python local_Python thread local

由于GIL的原因,笔者在日常开发中几乎没有用到python的多线程。如果需要并发,一般使用多进程,对于IO Bound这种情况,使用协程也是不错的注意。但是在python很多的网络库中,都支持多线程,基本上都会使用到threading.local。在python中threading.local用来表示线程相关的数据,线程相关指的是这个属性再各个线程中是独立的 互不影响,先来看一个最简答的例子:

1 classWidgt(object):2 pass

3

4 importthreading5 deftest():6 local_data =threading.local()7 #local_data = Widgt()

8 local_data.x = 1

9

10 defthread_func():11 print('Has x in new thread: %s' % hasattr(local_data, 'x'))12 local_data.x = 2

13

14 t = threading.Thread(target =thread_func)15 t.start()16 t.join()17 print('x in pre thread is %s' %local_data.x)18

19 if __name__ == '__main__':20 test()

输出:

Has x in new thread: False

x in pre thread is 1

可以看到,在新的线程中 local_data 并没有x属性,并且在新线程中的赋值并不会影响到其他线程。也可以稍微改改代码,去掉第7行的注释,local_data就变成了线程共享的变量。

local怎么实现的呢 在threading.py 代码如下:

1 try:2 from thread import_local as local3 exceptImportError:4 from _threading_local import local

可以看到,local是python的buildin class,同时也提供了一个纯python版本的参考实现,在_threading_local.py,我们来看看代码(代码不全 省略了几个函数):

1 class_localbase(object):2 __slots__ = '_local__key', '_local__args', '_local__lock'

3

4 def __new__(cls, *args, **kw):5 self = object.__new__(cls)6 key = '_local__key', 'thread.local.' + str(id(self)) #产生一个key,这个key在同一个进程的多个线程中是一样的

7 object.__setattr__(self, '_local__key', key)8 object.__setattr__(self, '_local__args', (args, kw))9 object.__setattr__(self, '_local__lock', RLock()) #可重入的锁

10

11 if (args or kw) and (cls.__init__ is object.__init__):12 raise TypeError("Initialization arguments are not supported")13

14 #We need to create the thread dict in anticipation of

15 #__init__ being called, to make sure we don't call it

16 #again ourselves.

17 dict = object.__getattribute__(self, '__dict__')18 current_thread().__dict__[key] = dict #在current_thread这个线程唯一的对象的—__dict__中加入 key

19

20 returnself21

22 def_patch(self):23 key = object.__getattribute__(self, '_local__key')24 d = current_thread().__dict__.get(key) #注意 current_thread 在每一个线程是不同的对象

25 if d is None: #在新的线程第一次调用时

26 d = {} #一个空的dict !!!

27 current_thread().__dict__[key] =d28 object.__setattr__(self, '__dict__', d) #将实例的__dict__赋值为 线程独立的一个字典

29

30 #we have a new instance dict, so call out __init__ if we have

31 #one

32 cls =type(self)33 if cls.__init__ is not object.__init__:34 args, kw = object.__getattribute__(self, '_local__args')35 cls.__init__(self, *args, **kw)36 else:37 object.__setattr__(self, '__dict__', d)38

39 classlocal(_localbase):40

41 def __getattribute__(self, name):42 lock = object.__getattribute__(self, '_local__lock')43 lock.acquire()44 try:45 _patch(self) #这条语句执行之后,self.__dict__ 被修改成了线程独立的一个dict

46 return object.__getattribute__(self, name)47 finally:48 lock.release()

代码中 已经加入了注释,便于理解。总结就是,在每个线程中增加一个独立的dict(通过current_thread()这个线程独立的对象),然后每次对local实例增删改查的时候,进行__dict__的替换。我们看看测试代码:

1 importthreading2 from _threading_local importlocal3 deftest():4 local_data =local()5 local_data.x = 1

6 print 'id of local_data', id(local_data)7

8 defthread_func():9 before_keys = threading.current_thread().__dict__.keys()10 local_data.x = 2

11 after = threading.current_thread().__dict__

12 #print set(after.keys()) - set(before.keys())

13 print [(e, v) for (e, v) in after.iteritems() if e not inbefore_keys]14

15 t = threading.Thread(target =thread_func)16 t.start()17 t.join()18 print('x in pre thread is %s' %local_data.x)19

20 if __name__ == '__main__':21 test()

输出:

id of local_data 40801456

[(('_local__key', 'thread.local.40801456'), {'x': 2})]

从输出可以看到,在这次运行总,local_data的id是40801456,在每个线程中都是一样的。在新的线程(thread_func函数)中访问local_data对象之前,current_thread()返回的对象是没有__local_key的,在第10行访问的时候会增加这个属性(_patch函数中)。

在gevent中,也有一个类叫local,其作用是提供协程独立的数据。PS:gevent中提供了几乎与python原生协程一样的数据结构,如Event、Semaphore、Local,而且,gevent的代码和文档中也自称为“thread”,这点需要注意。gevent.local的实现借鉴了上面介绍的_threading_local.py, 区别在于,_threading_local.local 将线程独立的数据存放在current_thread()中,而gevent.local将协程独立的数据存放在greenlet.getcurrent()中。

最后,如果在代码中使用了gevent.monkey.patch_all(),那么python原生的threading.local将会被替换成gevent.local.local。之前在看bottle的代码的时候,发现里面都是使用的threading.local,当时也对monkey_patch具体patch了那些模块不了解,于是就想如果使用gevent是否会出错呢,结果测试了很久都发现没问题,直到重新细看bottle源码才发现原因所在。代码如下:

1 classGeventServer(ServerAdapter):2 """Untested. Options:

3

4 * See gevent.wsgi.WSGIServer() documentation formore options.5 """6

7 def run(self, handler):8 fromgevent import pywsgi, local9 ifnot isinstance(threading.local(), local.local): #注意这里10 msg = "Bottle requires gevent.monkey.patch_all() (before import)"

11 raise RuntimeError(msg)12 ifself.quiet:13 self.options['log'] =None14 address =(self.host, self.port)15 server = pywsgi.WSGIServer(address, handler, **self.options)16 if 'BOTTLE_CHILD' inos.environ:17 import signal18 signal.signal(signal.SIGINT, lambda s, f: server.stop())19 server.serve_forever()

这个小插曲其实也反映了monkey-patch的一些优势与劣势。其优势在于不对源码修改就能改变运行时行为,提高性能;同时 ,对于缺乏经验或者对patch细节不了解的人来说,会带来静态代码与运行结果之间的认知差异。

references:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值