前段时间写了个多线程的程序,了解到Python中有个与众不同的thread.local()方法,可以创建一个全局对象,各个线程可以用这个全局对象保存各自的局部变量,而在使用时不受其他线程的影响。于是抽时间分析了一下thread.local()方法的源码。

相关知识储备:

__slots__变量:__slots__变量一个元组,可以限制该类可使用的成员变量,不在__slots__变量中的成员变量名不能被动态添加到该类中。

参考:https://www.cnblogs.com/zhaoshizi/p/9384430.html

Python上下文管理器contextmanager:引入contextlib模块中的contextmanager,使用contextmanager来注解一个函数,则可以用with语句来调用该函数,在函数中遇到yield时,在yield处停止,并把yield后的值返回。当执行完with的所有的语句后,再执行yield后面的语句。

参考:https://www.jb51.net/article/92387.htm

Python弱引用weakref:你可以通过调用weakref模块的ref(obj[,callback])来创建一个弱引用,obj是你想弱引用的对象,callback是一个可选的函数,当因没有引用导致Python要销毁这个对象时调用

参考:https://segmentfault.com/a/1190000005729873

下面进入正题,thread.local类原理分析(不同版本的Python该类的实现方式可以不同,本文以Python3.6为例):

thread.local类使用了一个_localimpl类,来作为各线程局部变量的管理类,该类的作用是保存各个线程的局部变量。那如何让各个线程的局部变量相互独立且不相互影响?没错,答案就是用字典类型变量。每个线程分配一个字典项,字典项的key为线程的id,value值保存的是该线程局部变量组成的另一个字典。_localimpl类中通过__slots__变量限制定义了几个成员变量,包括locallock(锁)和dicts(字典),dicts变量中就如上所述保存了各线程的局部变量构成的字典。所以dicts是字典的字典。

为了保证线程被删除时,对应的局部变量也会被删除,_localimpl类中还定义了一个该线程的弱引用wrthread,当该线路引用数为0时,调用回调函数thread_deleted删除dicts字典中该线程的记录项。

 class _localimpl:
"""A class managing thread-local dicts"""
"""管理线程局部字典的类"""
__slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__' def __init__(self):
# The key used in the Thread objects' attribute dicts.
# We keep it a string for speed but make it unlikely to clash with
# a "real" attribute.
self.key = '_threading_local._localimpl.' + str(id(self))
# { id(Thread) -> (ref(Thread), thread-local dict) }
#字典变量中的值中以id(Thread)为key,当前线程的弱引用ref(Thread)和
#当前线程的局部变量字典(thread-local dict)组成的元组
self.dicts = {} def get_dict(self):
"""Return the dict for the current thread. Raises KeyError if none
defined."""
"""返回当前线程的局部变量字典,其是元组中的第二个元素"""
thread = current_thread()
return self.dicts[id(thread)][1] def create_dict(self):
"""Create a new dict for the current thread, and return it."""
"""为当前线程造建一个局部变量字典,用来保存局部变量"""
localdict = {}
key = self.key
thread = current_thread()
idt = id(thread)
def local_deleted(_, key=key):
# When the localimpl is deleted, remove the thread attribute.
# 定义一个弱引用的回调函数,当线程局部变量的管理类localimpl被删除时,从线程中删除对应的变量
thread = wrthread()
if thread is not None:
del thread.__dict__[key]
def thread_deleted(_, idt=idt):
# When the thread is deleted, remove the local dict.
# Note that this is suboptimal if the thread object gets
# caught in a reference loop. We would like to be called
# as soon as the OS-level thread ends instead.
# 定义一个弱引用的回调函数,当线程被删除时,在管理类localimpl对象的字典中删除该线程的字典项
local = wrlocal()
if local is not None:
dct = local.dicts.pop(idt)
#定义两个弱引用
wrlocal = ref(self, local_deleted)
wrthread = ref(thread, thread_deleted)
#线程和局部变量管理类相互关联
thread.__dict__[key] = wrlocal
#在字典中以线程id为key,保存了弱引用和线程局部变量的字典
self.dicts[idt] = wrthread, localdict
return localdict
local类中通过__slots__变量定义了两个变量_local__impl和__dict__,_local__impl用来保存上述局部变量管理类_localimpl,__dict__则保存local类自己的成员函数或变量,可以通过self.xxx来引用到。local类中重载了__getattribute__、__setattr__、__delattr__等函数,通过重载这些函数,将local类中的__dict__对象指向了_local__impl中保存的该线程的局部变量字典,对local类进行赋值和获取操作时,实际上是对_local__impl中保存的该线程的局部变量字典进行操作。
 #定义上下文管理器,用来做统一的预处理,把_local__impl类中的保存的该线程的字典值赋值为该线程的__dict__,local类就可以对局部变量进行操作了。
@contextmanager
def _patch(self):
#获取_local__impl值
impl = object.__getattribute__(self, '_local__impl')
try:
#获取该线程的局部变量字典
dct = impl.get_dict()
except KeyError:
dct = impl.create_dict()
args, kw = impl.localargs
self.__init__(*args, **kw)
#操作时加锁
with impl.locallock:
#将局部变量dict值赋值给__dict__
object.__setattr__(self, '__dict__', dct)
#跳过_patch继续执行with语句块,执行完成后再执行yield后面的语句(退出)
yield class local:
#只定义了两个变量,局部变量存在__dict__指向的字典里
__slots__ = '_local__impl', '__dict__' def __new__(cls, *args, **kw):
if (args or kw) and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported")
self = object.__new__(cls)
impl = _localimpl()
impl.localargs = (args, kw)
impl.locallock = RLock()
object.__setattr__(self, '_local__impl', impl)
# We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
# again ourselves.
impl.create_dict()
return self #每个取值操作都用调用该函数
def __getattribute__(self, name):
#从局部变量管理类中获取该线程的局部变量字典,赋值给__dict__
with _patch(self):
#从__dict__中获取局部变量值
return object.__getattribute__(self, name) def __setattr__(self, name, value):
if name == '__dict__':
raise AttributeError(
"%r object attribute '__dict__' is read-only"
% self.__class__.__name__)
with _patch(self):
return object.__setattr__(self, name, value) def __delattr__(self, name):
if name == '__dict__':
raise AttributeError(
"%r object attribute '__dict__' is read-only"
% self.__class__.__name__)
with _patch(self):
return object.__delattr__(self, name) from threading import current_thread, RLock

threading.local()源码分析的更多相关文章

  1. python语言线程标准库threading.local源码解读

    本段源码可以学习的地方: 1. 考虑到效率问题,可以通过上下文的机制,在属性被访问的时候临时构建: 2. 可以重写一些魔术方法,比如 __new__ 方法,在调用 object.__new__(cls ...

  2. python线程threading.Timer源码解读

    threading.Timer的作用 官方给的定义是: """Call a function after a specified number of seconds: t ...

  3. Flask系列10-- Flask请求上下文源码分析

    总览 一.基础准备. 1. local类 对于一个类,实例化得到它的对象后,如果开启多个线程对它的属性进行操作,会发现数据时不安全的 import time from threading import ...

  4. SqlAlchemy 中操作数据库时session和scoped_session的区别(源码分析)

    原生session: from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from sqlalch ...

  5. C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库

    C# DateTime的11种构造函数   别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...

  6. SDN实验---Ryu的源码分析

    一:安装Pycharm https://www.cnblogs.com/huozf/p/9304396.html(有可取之处) https://www.jetbrains.com/idea/buy/# ...

  7. Flask框架(三)—— 请求扩展、中间件、蓝图、session源码分析

    Flask框架(三)—— 请求扩展.中间件.蓝图.session源码分析 目录 请求扩展.中间件.蓝图.session源码分析 一.请求扩展 1.before_request 2.after_requ ...

  8. django源码分析——本地runserver分析

    本文环境python3.5.2,django1.10.x系列 1.根据上一篇文章分析了,django-admin startproject与startapp的分析流程后,根据django的官方实例此时 ...

  9. [源码分析] 分布式任务队列 Celery 多线程模型 之 子进程

    [源码分析] 分布式任务队列 Celery 多线程模型 之 子进程 目录 [源码分析] 分布式任务队列 Celery 多线程模型 之 子进程 0x00 摘要 0x01 前文回顾 1.1 基类作用 1. ...

随机推荐

  1. alias-unalias

    一.用一条命令完成创建目录/data/test,即在/目录下创建/data目录,及其子目录/data/test 解答:mkdir -p /data/test 实践过程: 二.已知/tmp目录下已经存在 ...

  2. CSS 实现单、多行文本溢出显示省略号(…)

    如果实现单行文本的溢出显示省略号同学们应该都知道用text-overflow:ellipsis属性来,当然还需要加宽度width属来兼容部分浏览. 实现方法: overflow: hidden; te ...

  3. Python全栈之路----常用模块----sys模块

    sys.argv  命令行参数 List,第一个元素是程序本身路径 #test.py import sys print(sys.argv) D:\ProgramLearning\Py_program& ...

  4. java-ArrayList中去重复字符串或重复对象、LinkedList集合、泛型、增强for、静态导入、可变参数、asList()方法、集合嵌套

    1.去除ArrayList中重复字符串元素方式 * A:案例演示 * 需求:ArrayList去除集合中字符串的重复值(字符串的内容相同) * 思路:创建新集合方式 /** * A:案例演示 * 需求 ...

  5. python while循环案例

    1.while循环语句基本结构? while condition: loop body 2.利用while语句写出猜大小的游戏: 设定一个理想数字比如:66,让用户输入数字,如果比66大,则显示猜测的 ...

  6. HTML前序

    HTML基本格式 <!DOCTYPE html> //文档类型声明 <html lang="zh-cn"> //表示HTML文档开始,属性lang,属性值= ...

  7. UltraISO 9.7.1.3519注册码

    王涛 7C81-1689-4046-626F redcaps 82C6-3DEF-AB07-0EC0

  8. json转对象,奇怪的映射

    偶然看见此代码,记录下,将来可能会用到. ObjectMapper objectMapper = new ObjectMapper(); if (StringUtils.isNotEmpty(vari ...

  9. 如何判断win10 和office的版本

    1:区分win10的版本 2:查询office 的版本

  10. 涂抹mysql笔记-数据库中的权限体系

    涂抹mysql笔记-数据库中的权限体系<>能不能连接,主机名是否匹配.登陆使用的用户名和密码是否正确.mysql验证用户需要检查3项值:用户名.密码和主机来源(user.password. ...