Flask:local
threading.local
多线程下共享数据的两个方式:管道和共享变量,共享变量需要加锁(防止数据写乱)。threading.loacl类,以每条线程的ID号为字典的key,对应val(数据),在不加锁的情况下,能保证多个线程修改同一数据,复制多份变量给每个线程用,为每个线程开辟一块空间进行数据存储。
进程和线程有ID号,协程没有,flask重写了threading.loacl,也支持协程。因此flask框架用的全局request,每来一个请求启一条线程,不同线程用的是自己当前线程的request,所以数据不会乱。
不用threading.local
10条线程正常应该打印0-9, 但中间阻塞了2秒,直到数据改为9,全部打印的9,数据乱了
from threading import Thread
import time
lqz = -1
def task(arg):
global lqz
lqz = arg
time.sleep(2)
print(lqz)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
使用threading.local
lqz是local对象,它以线程id为key存储数据{'线程id':{value: 0}, '线程id':{value: 1}, '线程id':{value: 2}......},在自己线程内打印拿到的就是自己的value,没有加锁,数据也不会乱
from threading import Thread
from threading import local
import time
# 特殊对象, local实例化得到对象
lqz = local()
def task(arg):
# 对象.val = 1/2/3/4/5
lqz.value = arg
time.sleep(2)
print(lqz.value)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
通过字典自定义threading.local(函数版)
from threading import Thread, get_ident
import time
storage = {}
def set(k ,v):
# 按照 {'线程id':{val: 0}, '线程id':{val: 1}, '线程id':{val: 2}...} 构造数据
ident = get_ident() # 获取当前线程id号
if ident in storage:
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) # 存值
time.sleep(2)
v = get('val') # 取值
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
通过字典自定义threading.local(面向对象版)
from threading import Thread, get_ident
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)
time.sleep(2)
v = obj.get('val')
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
通过字典自定义threading.local(接近local)
通过setattr和getattr实现,有一个问题:storage是类属性,生成obj1和obj2共用一个名称空间,即共用一个字典(storage)
from threading import Thread, get_ident
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, key):
ident = get_ident()
return Local.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
time.sleep(2)
print(obj.val)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
通过字典自定义threading.local(终极版)
把storage放到__init__中,每次实例化时初始化得到一个字典,每次实例化得到local对象,用自己的字典存储
from threading import Thread, get_ident
import time
class Local(object):
def __init__(self):
"""
不能以self.storage={} 方式给storage赋值
当obj.val=arg赋值时,自动触发__setattr__方法,方法中调用self.storage,又触发__setattr__,形成无限递归调用
"""
# self.storage = {}
"""
object类调也有__setattr__方法,类调用绑定给对象的方法,就是调用普通函数, 有几个参数传几个
Local初始化时,执行下面代码,把key和value放到对象中,相当于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()
return self.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
time.sleep(2)
print(obj.val)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
通过字典自定义threading.local(支持协程)
终极版用的是线程id号,不支持协程,协程是单线程下实现并发,每一个线程id号对应多个协程,需要拿到协程id号
"""
getcurrent获取协程id,重命名为get_ident
如果开了协程,get_ident拿到的就是协程id,否则拿到的就是线程id
"""
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):
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()
return self.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
time.sleep(2)
print(obj.val)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()