十二、threading.local
1、线程之间资源共享和threading.local
代码:
1 from threading import local, Thread 2 3 4 i = None 5 6 7 def func(num): 8 global i 9 i = num 10 print(i) 11 12 13 threads = [] 14 for i in range(10): 15 threads.append(Thread(target=func, args=(i, ))) 16 17 for thread in threads: 18 thread.start() 19 20 for thread in threads: 21 thread.join()
输出:
由于没有阻塞操作,导致看不出没有给数据加锁会造成什么影响,
加入阻塞操作后的代码:
1 from threading import local, Thread 2 from time import sleep 3 4 5 i = None 6 7 8 def func(num): 9 global i 10 i = num 11 sleep(2) 12 print(i) 13 14 15 threads = [] 16 for i in range(10): 17 threads.append(Thread(target=func, args=(i, ))) 18 19 for thread in threads: 20 thread.start() 21 22 for thread in threads: 23 thread.join()
输出:
由于加入阻塞操作后,所有的线程在未输入i之前,争夺资源i,使得每个线程最终输出的i都是最后一次修改的数据,
那么有什么方法,可以使得每次在线程中访问同一个数据,但都是自己的资源,线程之间的数据相互隔离呢?
使用threading.local,
代码:
1 from threading import local as Local, Thread, RLock 2 from time import sleep 3 4 5 i = None 6 local = Local() 7 8 9 def func(num): 10 local.x = num 11 sleep(0.3) 12 print(local.x) 13 14 15 threads = [] 16 for i in range(10): 17 threads.append(Thread(target=func, args=(i, ))) 18 19 for thread in threads: 20 thread.start() 21 22 for thread in threads: 23 thread.join()
输出:
2、threading.local原理
threading.local是一个类,当实例化后,使用点号给对象某个属性赋值时就会调用__setattr__方法,使用点号获取某个属性值时就会调用__getattr__,
我们可以在local对象中设置一个类似于字典的属性,每当要设置某个属性值或者访问某个属性时,就在__setattr__或__getattr__中找到对应线程存储的值(或者说为每个访问环境设置一个唯一标识符,对应访问环境中的值),
函数实现:
1 from threading import Thread, get_ident 2 from time import sleep 3 4 storage = {} 5 6 7 def set(k, v): 8 # 获取当前线程或携程的唯一标识 9 ident = get_ident() 10 if ident in storage: 11 storage[ident][k] = v 12 else: 13 storage[ident] = {k:v} 14 15 def get(k): 16 ident = get_ident() 17 try: 18 res = storage[ident][k] 19 except Exception as e: 20 return None 21 else: 22 return res 23 24 25 def func(num): 26 set("x", num) 27 sleep(2) 28 print(get("x")) 29 30 31 threads = [] 32 for i in range(10): 33 threads.append(Thread(target=func, args=(i, ))) 34 35 for thread in threads: 36 thread.start() 37 38 for thread in threads: 39 thread.join()
输出:
错误的类实现:
1 from threading import Thread, get_ident 2 from time import sleep 3 4 5 class Local(object): 6 def __init__(self): 7 self.storage = {} 8 9 def __setattr__(self, key, value): 10 ident = get_ident() 11 if ident in self.storage: 12 self.storage[ident][key] = value 13 else: 14 self.storage[ident] = {key: value} 15 16 def __getattr__(self, item): 17 ident = get_ident() 18 try: 19 res = self.storage[ident][item] 20 except Exception as e: 21 return None 22 else: 23 return res 24 25 local = Local() 26 27 def func(num): 28 local.x = num 29 print(local.x) 30 31 32 threads = [] 33 for i in range(10): 34 threads.append(Thread(target=func, args=(i, ))) 35 36 for thread in threads: 37 thread.start() 38 39 for thread in threads: 40 thread.join()
报错信息:
原因:
不可以直接在初始化对象时,直接将对象设置storage为字典,因为当使用self.storage={}时,就会调用self.__setattr__("storage", {}),而第11行的storage属性还没有被设置,因而为NoneType类型
正确的类实现:
1 from threading import Thread, get_ident 2 from time import sleep 3 4 5 class Local(object): 6 def __init__(self): 7 object.__setattr__(self, "storage", {}) 8 9 def __setattr__(self, key, value): 10 ident = get_ident() 11 if ident in self.storage: 12 self.storage[ident][key] = value 13 else: 14 self.storage[ident] = {key: value} 15 16 def __getattr__(self, item): 17 ident = get_ident() 18 try: 19 res = self.storage[ident][item] 20 except Exception as e: 21 return None 22 else: 23 return res 24 25 local = Local() 26 27 def func(num): 28 local.x = num 29 print(local.x) 30 31 32 threads = [] 33 for i in range(10): 34 threads.append(Thread(target=func, args=(i, ))) 35 36 for thread in threads: 37 thread.start() 38 39 for thread in threads: 40 thread.join()
输出: