threading.local 对象
一. threading.local 对象
1.1 问题引入
多个线程操作同一个变量,如果不加锁,会出现数据错乱问题
1.2 作用
线程变量: 意思是threading.local中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。threading.local为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量, 从而防止数据错乱
二. 代码展示
2.1 不使用local对象 >>> 需加锁
------示例1------
from threading import Thread
import time
from threading import Lock
lqz = -1
def task(lock1):
lock1.acquire()
global lqz
tem = lqz
time.sleep(0.0001)
lqz = tem + 1
print('---', lqz)
lock1.release()
lock = Lock()
print(lqz)
for i in range(10):
t = Thread(target=task, args=(lock,))
t.start()
t.join()
print(lqz)
------示例2------
from threading import Thread
import time
from threading import Lock
lqz = -1
def task(lock1):
lock1.acquire()
global lqz
tem = lqz
time.sleep(0.0001)
lqz = tem + 1
print('---', lqz)
lock1.release()
lock = Lock()
print(lqz)
for i in range(10):
t = Thread(target=task, args=(lock,))
t.start()
t.join()
print(lqz)
2.1 使用local对象
from threading import Thread,get_ident
from threading import local
import time
# 特殊的对象
lqz = local()
def task(arg):
lqz.value = arg
time.sleep(0.1)
print('第:%s条线程的值为:%s'%(get_ident(),lqz.value))
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
三. 自定义local支持线程和协程
3.1 问题引入
1. flask 的request,和session 都是全局的,但是我们在不同的视图函数中使用的 是正对于当前这次请求的对象,它的底层就是基于local写的
2. flask部署支持多进程线程架构,也支持协程架构,原因时 flask 内部重写了 local,让它支持线程和协程
3.2 local实现的本质
1. 定义一个字典变量, 以线程或协程 id 做为 key, 相关数据做为 value 进行存储。
1. 定义变量
local: {'线程id号':{}}
2. 设置值
一个线程时:local.val='lqz' >>> {'线程1 id号':{val:lqz},}
多个线程时:local.val='pyy' >>> {'线程1 id号':{val:lqz}, '线程2 id号':{val:pyy},}
3. 取值
print(local.val) --->>> l = {'线程1id号':{val:lqz}, '线程2id号':{val:pyy},} --->>> 先当前线程的id号:get_ident() --->>> l[get_ident(线程1)]['val']
3.3 实现自定义 threading.local
1. 方式一:>>> 通过字典自定义threading.local
from threading import get_ident, Thread
import time
storage = {}
def set(k, v):
ident = get_ident() # 当前线程id号
if ident in storage: # 如果当前线程id号在字典中,表示修改值,直接改即可
storage[ident][k] = v
else: # 新增
storage[ident] = {k: v}
def get(k):
ident = get_ident()
return storage[ident][k]
def task(arg):
set('val', arg)
v = get('val')
time.sleep(0.01)
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
2. 方式二:>>> 面向对象
from threading import get_ident, Thread
import time
class Local(object):
storage = {}
def set(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def get(self, k):
ident = get_ident()
return Local.storage[ident][k]
obj = Local()
def task(arg):
obj.set('val', arg)
v = obj.get('val')
time.sleep(0.01)
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
3. 方式三:>>> 重写类的 __ setatt r__ __ getattr __
from threading import get_ident, Thread
import time
class Local(object):
storage = {}
def __setattr__(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return Local.storage[ident][k]
obj = Local() # 多个local对象公用一个storage
def task(arg):
obj.val = arg
v = obj.val
time.sleep(0.01)
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
4. 方式四:>>> 兼容线程和协程(完整版)
# 如果代码是以线程写的, 则使用的是获取线程号的方法。反之,则是获取协程号的方法
try:
from greenlet import getcurrent as get_ident
except Exception as e:
from threading import get_ident
from threading import Thread
import time
class Local(object):
def __init__(self):
# self.storage = {} # 只要self.属性,就会调用 __setattr__,内部又掉了self.storage--->递归了
# 类来调用对象的绑定方法__setattr__,这个方法就会变成函数,有几个值就要传几个值
# 本质就是完成 self.storage = {} 要完成的事,但是不会触发递归调用
# setattr(self,'storage', {}) # 反射的方式设置值,也会触发递归
object.__setattr__(self, 'storage', {})
def __setattr__(self, k, v):
ident = get_ident()
if ident in self.storage:
self.storage[ident][k] = v
else:
self.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident() # 在协程中,gevent中是获取协程id号,如果在线程中,获取的是线程id号
return self.storage[ident][k]
obj = Local() # 每个local对象,用自己的字典
def task(arg):
obj.val = arg
v = obj.val
time.sleep(0.01)
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()