关于本地线程(线程本地变量,线程本地存贮),如果你之前有使用过Flask框架,肯定会好奇,为什么在多线的模式的情况,每个线程都能保留自己一份独立的参数,而不会被其他线程的参数影响,(每个请求上下文中都有自己的内容)。
理论来说对于(单核多线程模式下)Flask Web应用来说,每一个请求就是一个独立的线程。请求之间的信息要完全隔离,避免冲突,这就需要用到Thread Local。ThreadLocal 中每一个变量中都会创建一个副本,每个线程都可以访问自己内部的副本变量。
比如:flask中我们的一个HTTP请求,都能保持独立自己的参数信息,flask中全局变量改为dict, 每一个线程都有对应的自己的key, 并将request作为value存放,
PS:通常我们的flask运行的是,一般有几个模式
单进程单线程 多进程多线程 单进程多线程 单进程多协程
但是在我们的ThreadLocal对于协程的方式是不支持的!后续再梳理一下,在flask中,听闻作者是自己自定义一个Local用于对协程的支持!(比较我们的并发模式有很多种)下我们的Local一些知识点。
PS:python 自带的ThreadLocal只能实现基于线程的并发
在python中一个ThreadLocal是一个全局变量,但是它是一种特殊的对象,它的状态对线程隔离。
一个线程中的局部变量只有线程自身可以访问,同一个进程下的其他线程不可访问,每个线程都只能读写自己线程的独立副本,这样可以做到线程隔离,做相关数据防污染!这样就可以利用它来保存属于线程自己内部的私有数据。
意思就是:每个线程对一个Thread Local对象的修改都不会影响其他的线程。
ThreadLocal对象实现原理:以线程的ID来保存多份状态字典。
PS:当然也可以改为我们的协程的!指需要内部改为协程的ID!
简单示例:
import threading
# 获取Thread Local对象
local_storage = threading.local()
local_storage.number = 100
local_storage.request = "请求对象"
print('初始化',local_storage.number)
print('初始化',local_storage.request)
# 定义修改变量的线程
class TestThread(threading.Thread):
def run(self):
print('开始修改local_storage.number')
local_storage.number = 10002
print('开始修改local_storage.request')
local_storage.request = "我是线程内独立的一个请求对象"
import time
time.sleep(1)
print(local_storage.number)
print(local_storage.request)
print(">"*20)
another = TestThread()
another.start() # 打印2
another.join()
print('在主线程里面的值并没有被修改: ',local_storage.number) # 但是在主线程里面的值并没有被修改 打印1
print('在主线程里面的值并没有被修改: ',local_storage.request) # 但是在主线程里面的值并没有被修改 打印1
输出结果为:
初始化 100
初始化 请求对象
开始修改local_storage.number
开始修改local_storage.request
10002
我是线程内独立的一个请求对象
>>>>>>>>>>>>>>>>>>>>
在主线程里面的值并没有被修改: 100
在主线程里面的值并没有被修改: 请求对象
自己模拟顶一个类似的ThreadLocal,之前,关于字节码执行的是,其实我们的了解到过了关于python字典是否是线程安全的问题。
如果验证字典是否是线程的安装的,主要是看原子操作,写操作的是否分多个STORE_SUBSCR字节码。
分析字典的添加是否是线程安全显示 acton() 的反汇编:
storage = {}
def acton():
global storage
storage={"xiaozhong",1121}
import dis
dis.dis(acton)
输出的结果为:
18 0 LOAD_CONST 1 ('xiaozhong')
2 LOAD_CONST 2 (1121)
4 BUILD_SET 2
6 STORE_GLOBAL 0 (storage)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
字节码指令参考官网:https://docs.python.org/zh-cn/3/library/dis.html
从上面分析看字典设置字典值的时候是线程安全。
LOAD_CONST(consti)
将 co_consts[consti] 推入栈顶。
BUILD_SET(count)
类似于 BUILD_TUPLE 但会创建一个集合。
STORE_GLOBAL(namei)
类似于 STORE_NAME 但会将 name 存储为全局变量。
RETURN_VALUE
返回 TOS 到函数的调用者。
所以我们可以使用字典的形式来模拟:
from _thread import get_ident
import threading
import time
class MyThreadLocal(object):
def __init__(self):
# 自定义用于存贮对象的字典
self.storage = {}
# 获取当前线程的ID对象
self.get_ident = get_ident
#
def set(self, k, v):
# 获取到线下的IDident
ident = self.get_ident()
# print("线程自己的ID",ident)
# 判断是否已经存在的
if ident in self.storage:
# 存在就创建内部新的字典
self.storage[ident][k] = v
else:
# 不存在就创建,且创建新的内部字典用于存在自己内部的参数变量
self.storage[ident]={k: v}
def get(self, k):
ident = self.get_ident()
itemobj = self.storage.get(ident)
if not itemobj:
return None
return itemobj.get(k, None)
local_values = MyThreadLocal()
def action_task(num):
# 没一个线程保存自己的额内部的变量的信息
local_values.set('name', num)
# 设置线程内部需要的定义自己的变量
local_values.set('reques', '大爷的%s'%(num))
time.sleep(2)
# print(local_values.get('name'), threading.current_thread().name)
for i in range(3):
# th = threading.Thread(target=task, args=(i,))
th = threading.Thread(target=action_task, args=(i,), name='线程%s' % i)
th.start()
print(local_values.__dict__)
输出结果为:
{'storage': {9296: {'name': 0, 'reques': '大爷的0'}, 15760: {'name': 1, 'reques': '大爷的1'}, 12660: {'name': 2, 'reques': '大爷的2'}}, 'get_ident':
}
修改使用python魔法函数进行实现,补充几个魔法函数的说明:
class Test:
def __getattr__(self, key):
value = self[key]
if isinstance(value, dict):
value = Test(value)
return value
def __setattr__(self, key, value):
self.__dict__[key] = value
def __getitem__(self, item):
return self.__dict__[item]
def __setitem__(self, key, value):
self.__dict__[key] = value
s = Test()
s.name = "后端"# 调用__setattr__ 方法
print(s.name) # 会调用__getattr__方法
s.age = 991 # 调用__setattr__ 方法
print(s.age)
print(s.name) # 会调用__getattr__方法
print(s['age']) # 调用 __getitem__方法 输出1
s['name'] = 'tom' # 调用 __setitem__ 方法
修改我们的自定义的本地线程类:参考我们的flask内部自定义的本地线程类的源码
flask内部自定义的本地线程类的源码:
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
def release_local(local):
"""Releases the contents of the local for the current context.
This makes it possible to use locals without a manager.
Example::
>>> loc = Local()
>>> loc.foo = 42
>>> release_local(loc)
>>> hasattr(loc, 'foo')
False
With this function one can release :class:`Local` objects as well
as :class:`LocalStack` objects. However it is not possible to
release data held by proxies that way, one always has to retain
a reference to the underlying local object in order to be able
to release it.
.. versionadded:: 0.6.1
"""
local.__release_local__()
class Local(object):
__slots__ = ("__storage__", "__ident_func__")
def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
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)
我们自己定义的源码:
from _thread import get_ident
import threading
import time
class MyThreadLocal(object):
def __init__(self):
# 自定义用于存贮对象的字典
object.__setattr__(self,'storage',{})
# self.storage = {}
def __setattr__(self, key, value):
# 获取到线下的IDident
ident = get_ident()
# print("线程自己的ID",ident)
# 判断是否已经存在的
if ident in self.storage:
# 存在就创建内部新的字典
self.storage[ident][key] = value
else:
# 不存在就创建,且创建新的内部字典用于存在自己内部的参数变量
self.storage[ident] = {key: value}
def __getattr__(self, key):
ident = self.get_ident()
return self.storage.get(ident)[key]
def __delattr__(self, key):
try:
ident = self.get_ident()
del self.storage[ident][key]
except KeyError:
raise AttributeError(key)
local_values = MyThreadLocal()
def action_task(num):
# 没一个线程保存自己的额内部的变量的信息
local_values.name ="你没的%s"%(num)
local_values.reques = '大爷的%s'%(num)
# 设置线程内部需要的定义自己的变量
time.sleep(2)
# print(local_values.get('name'), threading.current_thread().name)
for i in range(3):
# th = threading.Thread(target=task, args=(i,))
th = threading.Thread(target=action_task, args=(i,), name='线程%s' % i)
th.start()
print(local_values.__dict__)
输出结果:
{'storage': {8200: {'name': '你没的0', 'reques': '大爷的0'}, 16696: {'name': '你没的1', 'reques': '大爷的1'}, 10464: {'name': '你没的2', 'reques': '大爷的2'}}}
总结:Thread通常所谓本地的变量,主要是为每个线程(也可以是协程)内部创建一个字典,字典内部再存储我们的需要定义属于线程自己的内部的变量信息。
个人其他博客地址
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
小钟同学 | 文 【原创】| QQ:308711822
1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。 2:关于文章内容,有部分内容参考自互联网整理,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。