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

 class Widgt(object):
pass import threading
def test():
local_data = threading.local()
# local_data = Widgt()
local_data.x = 1 def thread_func():
print('Has x in new thread: %s' % hasattr(local_data, 'x'))
local_data.x = 2 t = threading.Thread(target = thread_func)
t.start()
t.join()
print('x in pre thread is %s' % local_data.x) if __name__ == '__main__':
test()
输出:
    Has x in new thread: False
    x in pre thread is 1
 
  可以看到,在新的线程中 local_data 并没有x属性,并且在新线程中的赋值并不会影响到其他线程。也可以稍微改改代码,去掉第7行的注释,local_data就变成了线程共享的变量。
 
  local怎么实现的呢 在threading.py 代码如下:
  

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

 class _localbase(object):
__slots__ = '_local__key', '_local__args', '_local__lock' def __new__(cls, *args, **kw):
self = object.__new__(cls)
key = '_local__key', 'thread.local.' + str(id(self)) # 产生一个key,这个key在同一个进程的多个线程中是一样的
object.__setattr__(self, '_local__key', key)
object.__setattr__(self, '_local__args', (args, kw))
object.__setattr__(self, '_local__lock', RLock()) # 可重入的锁 if (args or kw) and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported") # We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
# again ourselves.
dict = object.__getattribute__(self, '__dict__')
current_thread().__dict__[key] = dict # 在current_thread这个线程唯一的对象的—__dict__中加入 key return self def _patch(self):
key = object.__getattribute__(self, '_local__key')
d = current_thread().__dict__.get(key) # 注意 current_thread 在每一个线程是不同的对象
if d is None: # 在新的线程第一次调用时
d = {} # 一个空的dict !!!
current_thread().__dict__[key] = d
object.__setattr__(self, '__dict__', d) # 将实例的__dict__赋值为 线程独立的一个字典 # we have a new instance dict, so call out __init__ if we have
# one
cls = type(self)
if cls.__init__ is not object.__init__:
args, kw = object.__getattribute__(self, '_local__args')
cls.__init__(self, *args, **kw)
else:
object.__setattr__(self, '__dict__', d) class local(_localbase): def __getattribute__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self) # 这条语句执行之后,self.__dict__ 被修改成了线程独立的一个dict
return object.__getattribute__(self, name)
finally:
lock.release()
   代码中 已经加入了注释,便于理解。总结就是,在每个线程中增加一个独立的dict(通过current_thread()这个线程独立的对象),然后每次对local实例增删改查的时候,进行__dict__的替换。我们看看测试代码:
  

 import threading
from _threading_local import local
def test():
local_data = local()
local_data.x = 1
print 'id of local_data', id(local_data) def thread_func():
before_keys = threading.current_thread().__dict__.keys()
local_data.x = 2
after = threading.current_thread().__dict__
# print set(after.keys()) - set(before.keys())
print [(e, v) for (e, v) in after.iteritems() if e not in before_keys] t = threading.Thread(target = thread_func)
t.start()
t.join()
print('x in pre thread is %s' % local_data.x) if __name__ == '__main__':
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源码才发现原因所在。代码如下:
  

 class GeventServer(ServerAdapter):
""" Untested. Options: * See gevent.wsgi.WSGIServer() documentation for more options.
""" def run(self, handler):
from gevent import pywsgi, local
if not isinstance(threading.local(), local.local): #注意这里
msg = "Bottle requires gevent.monkey.patch_all() (before import)"
raise RuntimeError(msg)
if self.quiet:
self.options['log'] = None
address = (self.host, self.port)
server = pywsgi.WSGIServer(address, handler, **self.options)
if 'BOTTLE_CHILD' in os.environ:
import signal
signal.signal(signal.SIGINT, lambda s, f: server.stop())
server.serve_forever()

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

 
references:
 
 
 
 
 
 
 
 
 
 

Python thread local的更多相关文章

  1. TLS 与 python thread local

    TLS 先说TLS( Thread Local Storage),wiki上是这么解释的: Thread-local storage (TLS) is a computer programming m ...

  2. Python - python3.7新增的contextvars vs Thread local(threading.local)

    总结 和threading.local()类似.Python3.7新增. thread.local(): 不同线程,同一个变量保存不同的值. contextvars: 不同上下文,同一个变量保存不同的 ...

  3. [Python]threading local 线程局部变量小測试

    概念 有个概念叫做线程局部变量.一般我们对多线程中的全局变量都会加锁处理,这样的变量是共享变量,每一个线程都能够读写变量,为了保持同步我们会做枷锁处理.可是有些变量初始化以后.我们仅仅想让他们在每一个 ...

  4. Java进阶(七)正确理解Thread Local的原理与适用场景

    原创文章,始自发作者个人博客,转载请务必将下面这段话置于文章开头处(保留超链接). 本文转发自技术世界,原文链接 http://www.jasongj.com/java/threadlocal/ Th ...

  5. 线程TLAB局部缓存区域(Thread Local Allocation Buffer)

    TLAB(Thread Local Allocation Buffer) 1,堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的 2,Sun ...

  6. Java Thread Local – How to use and code sample(转)

    转载自:https://veerasundar.com/blog/2010/11/java-thread-local-how-to-use-and-code-sample/ Thread Local ...

  7. Python Thread related

    1.Thread.join([timeout]) Wait until the thread terminates. This blocks the calling thread until the ...

  8. python thread的join方法解释

    python的Thread类中提供了join()方法,使得一个线程可以等待另一个线程执行结束后再继续运行.这个方法还可以设定一个timeout参数,避免无休止的等待.因为两个线程顺序完成,看起来象一个 ...

  9. 【Python@Thread】queue模块-生产者消费者问题

    python通过queue模块来提供线程间的通信机制,从而可以让线程分项数据. 个人感觉queue就是管程的概念 一个生产者消费者问题 from random import randint from ...

随机推荐

  1. PS基础学习

    1.文件新建 (1).菜单栏新建,快捷键(Ctrl+N) (2).预设的使用--->一般用于网页制作,就选择Web,宽度,高度的单位,网页是像素 (3).分辨率的设置--->电脑网页屏幕分 ...

  2. 安卓能用的modebus CRC16计算,附上对应的C语言的CRC16(转)

    源:安卓能用的modebus CRC16计算,附上对应的C语言的CRC16 “源”即是原文地址,想了解作都更多文章及思想请移步到“源”.转过只是为了本人感兴趣的文章查找方便. 正文: 最近写安卓串口通 ...

  3. C# .NET中的 反射的应用

    C#中的映射 C#编译后的文件主要由IL代码和元数据组成,元数据为.NET组件提供了丰富的自描述特性,它使得我们可以在代码运行时获知组件中的类型等重要的信息.C#中这是通过一种称作映射(Reflect ...

  4. 后端数据库使用 Bomb方案

    不再需要阿里云进行部署: http://docs.bmob.cn/ios/faststart/index.html?menukey=fast_start&key=start_ios

  5. cpu95%,查找问题sql

    收到一封告警邮件: Load average on xxx_server reached critical threshold values - 169.5 Current Load Avg = 16 ...

  6. 第六百二十七天 how can I 坚持

    竟然能上google了,可惜手机装twitter装上登不上呢. 浏览浏览能学学英语啊. 今天又在家待了一天,得戒游戏了,找找能让我疯狂的事情,然后坚持去干吧,加油. 洗澡睡觉.

  7. Git 入门 ---- Git 与 SVN 区别

    一. Git 是什么?        Git 是目前世界上最先进的分布式版本控制系统     二. 基础知识 有中心的 SCM(Software Configuration Management) 服 ...

  8. 封装 INI 文件读写函数

    delphi读写ini文件实例 //--两个过程,主要实现:窗体关闭的时候,文件保存界面信息:窗体创建的时候,程序读取文件文件保存的信息. //--首先要uses IniFiles(单元) //--窗 ...

  9. 使用Intent创建Tab页面

    前面已经介绍了如何使用TabActivity来创建Activity布局,前面添加Tab页面使用了TabHost.TabSpec如下方法. setContent(int viewId):直接将指定Vie ...

  10. 第一部分 记事本搞定第一个C#程序和编译过程剖析

    记事本搞定第一个C#程序 进行下面三个步骤:编码,编译和托管运行. 1.记事本进行编码: using System; class Program{ public static void Main() ...