先說一下和flask沒有關系的:
我們都知道線程是由進程創建出來的,CPU實際執行的也是線程,那么線程其實是沒有自己獨有的內存空間的,所有的線程共享進程的資源和空間,共享就會有沖突,對於多線程對同一塊數據處理的沖突問題,一個辦法就是加互斥鎖,另一個辦法就是利用threadlocal
ThreadLocal 實現的思路就是給一個進程中的多個線程開辟空間來保存線程中特有的值
代碼實現:
1、簡單示例:
import threading # 實例化對象 local_values = threading.local() def func(num): # 給對象加屬性,這個屬性就會保存在當前線程開辟的空間中 local_values.name = num import time time.sleep(1) # 取值的時候也從當前線程開辟的空間中取值 print(local_values.name, threading.current_thread().name) for i in range(20): th = threading.Thread(target=func, args=(i,), name='線程%s' % i) th.start()
打印結果:
0 線程0 1 線程1 2 線程2 3 線程3 10 線程10 9 線程9 4 線程4 8 線程8 5 線程5 7 線程7 6 線程6 13 線程13 11 線程11 17 線程17 15 線程15 14 線程14 16 線程16 12 線程12 18 線程18 19 線程19
如果把對象換成一個類對象:
import threading # 如果是一個類對象,結果就完全不一樣 class Foo(object): def __init__(self): self.name = 0 local_values = Foo() def func(num): local_values.name = num import time time.sleep(1) print(local_values.name, threading.current_thread().name) for i in range(20): th = threading.Thread(target=func, args=(i,), name='線程%s' % i) th.start()
打印結果:
19 線程1 19 線程3 19 線程4 19 線程5 19 線程2 19 線程0 19 線程9 19 線程6 19 線程8 19 線程11 19 線程7 19 線程10 19 線程15 19 線程16 19 線程13 19 線程14 19 線程18 19 線程19 19 線程12 19 線程17
2、依據這個思路,我們自己實現給線程開辟獨有的空間保存特有的值
協程和線程都有自己的唯一標識get_ident,利用這個唯一標識作為字典的key,key對應的value就是當前線程或協程特有的值,取值的時候也拿這個key來取
import threading # get_ident就是獲取線程或協程唯一標識的 try: from greenlet import getcurrent as get_ident # 協程 # 當沒有協程的模塊時就用線程 except ImportError: try: from thread import get_ident except ImportError: from _thread import get_ident # 線程 class Local(object): def __init__(self): self.storage = {} self.get_ident = get_ident def set(self,k,v): ident = self.get_ident() origin = self.storage.get(ident) if not origin: origin = {k:v} else: origin[k] = v self.storage[ident] = origin def get(self,k): ident = self.get_ident() origin = self.storage.get(ident) if not origin: return None return origin.get(k,None) # 實例化自定義local對象對象 local_values = Local() def task(num): local_values.set('name',num) import time time.sleep(1) print(local_values.get('name'), threading.current_thread().name) for i in range(20): th = threading.Thread(target=task, args=(i,),name='線程%s' % i) th.start()
3、因為要不停的賦值取值,就想到了__setattr__和__getattr__方法,但是要注意初始化時賦值和__setattr__方法中賦值又可能產生遞歸的問題
import threading try: from greenlet import getcurrent as get_ident # 協程 except ImportError: try: from thread import get_ident except ImportError: from _thread import get_ident # 線程 class Local(object): def __init__(self): # 這里一定要用object來調用,因為用self調用的就會觸發__setattr__方法,__setattr__方法里 # 又會用self去賦值就又會調用__setattr__方法,就變成遞歸了 object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident) 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) local_values = Local() def task(num): local_values.name = num import time time.sleep(1) print(local_values.name, threading.current_thread().name) for i in range(20): th = threading.Thread(target=task, args=(i,),name='線程%s' % i) th.start()
我們再說回Flask,我們知道django中的request是直接當參數傳給視圖的,這就不存在幾個視圖修改同一個request的問題,但是flask不一樣,flask中的request是導入進去的,就相當於全局變量,每一個視圖都可以對它進行訪問修改取值操作,這就帶來了共享數據的沖突問題,解決的思路就是我們上邊的第三種代碼,利用協程和線程的唯一標識作為key,也是存到一個字典里,類中也是采用__setattr__和__getattr__方法來賦值和取值
分情況:
對於單進程單線程:沒有影響,基於全局變量
對於單進程多線程:利用threading.local() 對象
對於單進程單線程的多協程:本地線程對象就做不到了,要用自定義,就是上邊的第三個代碼
一共涉及到四個知識點:
1、唯一標識
2、本地線程對象
3、setattr和getattr
4、怎么實現