Flask上下文管理源碼分析
阿新 • • 發佈:2018-09-05
lee sta targe start func pre local ror elf
引出的問題
Flask如何使用上下文臨時把某些對象變為全局可訪問
首先我們做如下的幾種情況的假設
情況一:單進程單線程
這種情況可以基於全局變量存儲臨時的對象
情況二:單進程多線程
這種情況會出現多個線程共享全局的變量,為了每個線程中的數據不被其他線程修改,可以借助hreading.local對象,為每個線程做唯一的表示用來做鍵,請求的對象作為值來實現
多線程共享數據的問題
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()
我們可以看到最後把每個線程中對象中name值都變為了19,不能保證每個線程中對象中的值唯一
使用hreading.local對象可以對每個線程做唯一的表示可以解決上述的問題
importthreading 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()
可以看到每個線程中的值唯一
- 情況三:單進程單線程(多個協程)Flask 的上下文管理就是基於這種情況做的
在這種情況下使用上面的方法可以保證線程中的數據唯一,但是使用其內部創建多個協程後,hreading.local只能對線程作唯一的標示,協程是在單線程下切換的,所以多個協程還會出現共享數據的問題
解決的思路:為每個程做唯一的標示,我們可以通過python自帶的greenlet模塊中的getcurrent來實現
只需對上面的代碼做簡單的修改即可
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): 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_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()
測試的結果如下
使用面向對象中方法對其進行簡單的優化
在初始化的時候設置屬性的時候,為了避免循環引用,我們可以這樣做 object.__setattr__(self, ‘storage‘, {})
class Foo(object): def __init__(self): object.__setattr__(self, ‘storage‘, {}) # self.storage = {} def __setattr__(self, key, value): self.storage = {‘k1‘:‘v1‘} print(key,value) def __getattr__(self, item): print(item) return ‘df‘ obj = Foo() # obj.x = 123 # 對象.xx
修改後的代碼如下所示
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.__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上下文管理源碼分析