Local是什麼? 無論你接觸到的是threading.Local還是werkzeug.Local,它們都代表一種變數——每個線程自己的全局變數。 全局變數,一般位於進程的堆上。一個進程的所有線程都可以訪問同一個全局變數,因為它們共用著進程的地址空間,所以每一個線程都可以訪問,這也帶來了問題,如果多 ...
Local是什麼?
無論你接觸到的是threading.Local還是werkzeug.Local,它們都代表一種變數——每個線程自己的全局變數。
全局變數,一般位於進程的堆上。一個進程的所有線程都可以訪問同一個全局變數,因為它們共用著進程的地址空間,所以每一個線程都可以訪問,這也帶來了問題,如果多個線程同時訪問同一個變數,會對該變數的讀寫造成不可預估的結果,所以通常我們會使用鎖或其他的同步機制來控制線程之間對共用變數的訪問。當然了,這不是本文關註的地方。
說回Local,我們在開頭提到Local是線程自己的全局變數。所謂線程自己的,就是說該“全局變數”只有擁有的線程自己可以訪問,對於其它的線程是不可見的。怎麼理解這個定義呢?我們先來看一種場景:函數A處理完參數A,函數B要處理函數A處理過的參數A,那麼函數A就要把參數A傳遞給函數B。如果函數C也要接著處理這個參數呢,函數D也要呢?那麼參數A就要在這些函數之間不斷地傳遞,這些函數生命時也要提前聲明好參數。可想而知,如果有參數要在函數之間傳遞,那麼函數會變得很複雜,調用函數也很複雜。有沒有簡便的辦法呢?
其實我們在函數間傳遞參數,為的是要使這個參數對於需要的函數都可視,那麼將它變成一個全局變數不就得了?可是變成全局變數的話,其它的線程就會訪問到我這個全局變數,可能改變它的值,這不是本線程的本意,我只想一個人獨占它。這時,我們就需要一種變數,對於本線程而言,它應該是一個全局變數,對於進程的其它線程而言,它又像是一個局部變數。這就是使用Local的一種場景了,Local就是這樣一種變數。
如果在全局域定義了一個Local,那麼這個local其實並不是一個全局變數,每個線程訪問這個變數時,拿到的實際上都是本線程對應的Local。怎麼實現這種效果呢?其實很簡單,Local本身並不是一個變數,它還包含了一些操作。你可以這樣理解,每個進程都有一個全局的字典,每個線程本身有自己的線程ID,進程的所有線程都可以訪問這個全局的字典,那麼它們把自己的線程ID當做字典的key,把需要存儲的東西當做value,每個線程只能通過自己的key來訪問這個字典,那麼value本身就相當於一個線程獨占的全局變數啦!是不是?每個線程都怪怪地拿屬於自己的東西,一個全局的東西,這就相當於一個線程內部的全局變數。具體的代碼實現有所區別,但大體上是這個思路。
class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): object.__setattr__(self, '__storage__', {}) # 存放東西的全局字典 object.__setattr__(self, '__ident_func__', get_ident) # 每個線程的key def __iter__(self): return iter(self.__storage__.items()) def __call__(self, proxy): """Create a proxy for a name.""" return LocalProxy(self, proxy) # 這裡返回一個LocalProxy對象,LocalProxy是一個代理,代理Local對象。 def __release_local__(self): self.__storage__.pop(self.__ident_func__(), None) 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怎麼用?
偽代碼如下
local = Local() local.request = "i am a request" local.response = "i am a response" def work(): local.request = xxxx # 每個線程都只會訪問到屬於自己的request和response local.response = xxxx # 就算改變response,也只是改變本線程的值 if __name__ == "__main__": for i in range(10): Thread(target=work).start()
通過聲明一個全局的Local對象,然後像訪問對象的屬性一樣訪問你要保留的值。你可以這樣理解,Local相當於一個字典,我要通過自己定義的key,來訪問我需要的值,即調用local.key來獲取值。這樣使用起來其實很彆扭,明明我是定義一個值,卻變成像是訪問一個對象的屬性一樣,寫起來很奇怪,有時候也不好理解。能不能像定義一個全局變數一樣,直接使用一個Local變數呢?
# 我想要這種效果 request = "i am a request" response = "i am a response"
Local的__call__方法就是乾這件事的,使用__call__方法,我們可以讓一個Local變得看起來像一個全局變數。
# 你只需要調用Local對象的__call__方法 local = Local() local.request = "i am a request" my_request = local("request") # 註意,這裡傳入的字元串需要和上面保存時的一致
my_request # "i am a request"
my_request現在等同於local.request,比起local.request,my_request是不是看起來更像一個全局變數?但記住,它是一個“線程獨有的全局變數”。
LocalProxy是什麼?
local相當於一個字典,local.x的x相當於key,而LocalProxy代管了這把key和local,我們只需訪問LocaProxy本身,它自動用這把key去local字典查到值,返回給我們,這就是代理(proxy)
my_request實際上是一個LocalProxy,直接訪問my_request,它是一個"i am a request"字元串。前面我們提到Local對象可以通過local.xxx=value來存儲我需要的本地全局變數,這樣的local對象看起來就像一個字典,可以存儲任意的值。但是每次都通過local.xxx來獲取我們想要的值太麻煩了,我們需要一個對象來幫我們完成這個重覆性的動作,把key交給它,把字典交給它,我只要訪問它,它就通過key去字典中查值,然後把值返回給我。這樣子它對於我來說就像存儲的值本身一樣。這就是代理。
LocalProxy的原理就是這樣,它幫我們幹了到Local中查找值的方法,所以我們要把存儲local.xxx時的“xxx”這把打開local的key告訴代理,然後把local本身也告訴代理,這樣LocalProxy便有了鑰匙,和要打開的門,自然他就可以把門裡面的東西返回給我們了。從這個角度考慮,Local本身也可以看做是一個代理,它代理的是線程的全局變數,而它持有的key則是線程的id,它會通過id到全局的dict中查找本線程的全局變數,然後返回給我們。
class LocalProxy(object): __slots__ = ('__local', '__dict__', '__name__', '__wrapped__') def __init__(self, local, name=None): object.__setattr__(self, '_LocalProxy__local', local) # 要打開的門 object.__setattr__(self, '__name__', name) # 鑰匙 if callable(local) and not hasattr(local, '__release_local__'): # "local" is a callable that is not an instance of Local or # LocalManager: mark it as a wrapped function. object.__setattr__(self, '__wrapped__', local) def _get_current_object(self): """Return the current object. This is useful if you want the real object behind the proxy at a time for performance reasons or because you want to pass the object into a different context. """ if not hasattr(self.__local, '__release_local__'): return self.__local() try: return getattr(self.__local, self.__name__) # 通過key(name)到字典(local)中獲取value except AttributeError: raise RuntimeError('no object bound to %s' % self.__name__) @property def __dict__(self): try: return self._get_current_object().__dict__ except RuntimeError: raise AttributeError('__dict__') def __repr__(self): try: obj = self._get_current_object() except RuntimeError: return '<%s unbound>' % self.__class__.__name__ return repr(obj) def __bool__(self): try: return bool(self._get_current_object()) except RuntimeError: return False def __unicode__(self): try: return unicode(self._get_current_object()) # noqa except RuntimeError: return repr(self) def __dir__(self): try: return dir(self._get_current_object()) except RuntimeError: return [] def __getattr__(self, name): if name == '__members__': return dir(self._get_current_object()) return getattr(self._get_current_object(), name) # 通過key(name)到字典(local)中去查找真正的value,並返回 def __setitem__(self, key, value): self._get_current_object()[key] = value def __delitem__(self, key): del self._get_current_object()[key] if PY2: __getslice__ = lambda x, i, j: x._get_current_object()[i:j] def __setslice__(self, i, j, seq): self._get_current_object()[i:j] = seq def __delslice__(self, i, j): del self._get_current_object()[i:j] __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v) __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
LocalProxy中有許多方法,這些方法都是LocalProxy本身實現的一些通用的方法,這些方法不是對本身的調用,而是對代理值的調用。
我們也可以不調用Local的__call__方法構造LocalProxy,可以直接通過LocalProxy的構造函數構造一個LocalProxy,實質上是一樣的。
local = Local() local.request = "request" my_request = LocalProxy(local, "request") # 第二個參數要和local.xxx的xxx相同
LocalStack是什麼?
LocalStack和Local差不多,只不過Local像一個字典。LocalStack則是一個棧,存儲數據的方式不太一樣。可以認為它是一個線程獨有的一個全局棧。使用它不用擔心被進程的其它線程干擾。
class LocalStack(object): def __init__(self): self._local = Local() def __release_local__(self): self._local.__release_local__() def _get__ident_func__(self): return self._local.__ident_func__ def _set__ident_func__(self, value): object.__setattr__(self._local, '__ident_func__', value) __ident_func__ = property(_get__ident_func__, _set__ident_func__) del _get__ident_func__, _set__ident_func__ def __call__(self): def _lookup(): rv = self.top if rv is None: raise RuntimeError('object unbound') return rv return LocalProxy(_lookup) def push(self, obj): """Pushes a new item to the stack""" rv = getattr(self._local, 'stack', None) if rv is None: self._local.stack = rv = [] rv.append(obj) return rv def pop(self): """Removes the topmost item from the stack, will return the old value or `None` if the stack was already empty. """ stack = getattr(self._local, 'stack', None) if stack is None: return None elif len(stack) == 1: release_local(self._local) return stack[-1] else: return stack.pop() @property def top(self): """The topmost item on the stack. If the stack is empty, `None` is returned. """ try: return self._local.stack[-1] except (AttributeError, IndexError): return None
Local和線程安全的區別
Local並不代表著線程安全(Thread-Safe),線程安全更多的是強調多個線程訪問同一個全局變數時的同步機制,而Local代表的全局變數時線程獨占的,對於其他線程而言是不可見的,所以根本不存線上程安不安全的問題。Local永遠只會被本線程操作,所以如果硬是要下一個定義,那麼是線程安全的。