提要: 在Python多线程当中,其资源是共享的,我们通常在使用共有资源时会采取线程锁来确保共有资源在修改的时候达到我们的预期,避免出现问题。但是在Python中还有第二种方案,就是这次要说的threaing.local,它的理念其实跟flask的上下文管理器相似,就是每次线程要处理共有的资源时,local就会帮线程复制一个共有的资源供线程使用,而复制出来的资源是独立的,并不会被其他的资源修改后而影响,直到线程的介绍。
多线程: 我们先看下下面的代码,我们的预期结果是10个线程都有自己的对应值:0、1、2、3、4
4、5、6、7、8、9,即打印出来。
# coding=utf-8
from threading import Thread
import time
num = None
def task(i):
# 赋值
global num
num = i
# 做一些其他的事情
time.sleep(2)
# 取值
print(num)
if __name__ == '__main__':
for i in range(10):
Thread(target=task, args=(i,)).start()
如果赋值和取值的速度够快的话,是可以达到我们预期效果,然而实际开发中大部分实现的功能不会是赋值取值这么简单,肯定会做一些其他的操作,类比代码中的time.sleep(2),而每个线程一旦取值有延迟,在加上没有上锁,那么取出来的值就会和我们预想的结果差距很大,上面的最终结果是9、9、9、9、9、9、9、9、9、9
threading.local: 在看看threading.local实现的代码,还是要和上面的预期结果一样:
# coding=utf-8
from threading import Thread
from threading import local
import time
num = local()
def task(i):
# 赋值
num.value = i
# 做一些其他的事情
time.sleep(2)
# 取值
print(num.value)
if __name__ == '__main__':
for i in range(10):
Thread(target=task, args=(i,)).start()
上面执行后的结果是0、1、2、3、4、5、6、7、8、9,但是对应的顺序是随机的,这是因为线程的执行顺序本身就是随机的,但这并不是没有达到我们预期的结果,而是达到了我们预期的结果,每个线程都有自己对应的值!可以看到上面的代码并没有上锁,只是实例化了一个local对象,每当有线程要执行对num进行操作时,就会复制一个独立的资源供这个线程使用,他们之间互不影响,这就是threading.local的隔离作用。
自定义Local: 有了上边的了解了之后,我们可以手动实现一个类似的local对象:
# coding=utf-8
from threading import Thread
from threading import get_ident
import time
num = {}
def get(k):
id = get_ident()
return num[id][k]
def set(k, v):
id = get_ident()
if id in num:
num[id][k] = v
else:
num[id] = {k: v}
def task(i):
set("val", i)
time.sleep(2)
v = get("val")
print(v)
if __name__ == '__main__':
for i in range(10):
Thread(target=task, args=(i,)).start()
首先要明白每个线程都有自己对应的get_ident()时唯一的,然后我们利用这个特性来区分那个线程正在处理数据,把数据存在num字典中,最后利用get来取对应的值,利用set来对每个字典进行设置值,利用get_ident()的唯一性起到隔离作用,就完成了一个很不错的local对象了。
面向对象实现: 实现了线程和携程的功能!!!
# coding=utf-8
from threading import Thread
try:
from threading import get_ident # 线程
except Exception as e:
from greenlet import getcurrent as get_ident # 携程
import time
class Local(object):
def __init__(self):
# self.num = {} 会调用这个类里面的__setattr__会报错
object.__setattr__(self, "num", {}) # 通过调用父类的魔法方法
def __getattr__(self, k):
id = get_ident()
return self.num[id][k]
def __setattr__(self, k, v):
id = get_ident()
if id in self.num:
self.num[id][k] = v
else:
self.num[id] = {k: v}
obj = Local()
def task(i):
obj.val = i
time.sleep(2)
v = obj.val
print(v)
if __name__ == '__main__':
for i in range(10):
Thread(target=task, args=(i,)).start()