print python 如何加锁_深度解密Python单例模式
1. 認識單例模式
- 認識單例模式
- 1 單例模式含義
- 2 單例模式優點
- 3 單例模式缺點
- 4 單例模式應用
2. Python實現單例模式
- Python實現單例模式
- 1 多種實現方法
- 2 實例分析
3. 總結
- 總結
認識單例模式
1.1 單例模式含義
例模式,也叫單子模式,是一種常用的軟件設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候整個系統只需要擁有一個的全局對象,這樣有利于我們協調系統整體的行為。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息。這種方式簡化了在復雜環境下的配置管理。
實現單例模式的思路是:一個類能返回對象一個引用(永遠是同一個)和一個獲得該實例的方法(必須是靜態方法,通常使用getInstance這個名稱);當我們調用這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就創建該類的實例并將實例的引用賦予該類保持的引用;同時我們還將該類的構造函數定義為私有方法,這樣其他處的代碼就無法通過調用該類的構造函數來實例化該類的對象,只有通過該類提供的靜態方法來得到該類的唯一實例。
單例模式在多線程的應用場合下必須小心使用。如果當唯一實例尚未創建時,有兩個線程同時調用創建方法,那么它們同時沒有檢測到唯一實例的存在,從而同時各自創建了一個實例,這樣就有兩個實例被構造出來,從而違反了單例模式中實例唯一的原則。 解決這個問題的辦法是為指示類是否已經實例化的變量提供一個互斥鎖(雖然這樣會降低效率)。
1.2 單例模式優點
單例模式的優點: 1、由于單例模式要求在全局內只有一個實例,因而可以節省比較多的內存空間; 2、全局只有一個接入點,可以更好地進行數據同步控制,避免多重占用; 3、單例可長駐內存,減少系統開銷。
1.3 單例模式缺點
單例模式的缺點 1、單例模式的擴展是比較困難的; 2、賦于了單例以太多的職責,某種程度上違反單一職責原則(六大原則后面會講到); 3、單例模式是并發協作軟件模塊中需要最先完成的,因而其不利于測試; 4、單例模式在某種情況下會導致“資源瓶頸”。
1.4 單例模式應用
單例模式的應用舉例: 1、生成全局惟一的序列號; 2、訪問全局復用的惟一資源,如磁盤、總線等; 3、單個對象占用的資源過多,如數據庫等; 4、系統全局統一管理,如Windows下的Task Manager; 5、網站計數器。
Python實現單例模式
2.1 多種實現方法
2.1.1.使用模塊
其實,Python 的模塊就是天然的單例模式,因為模塊在第一次導入時,會生成 .pyc 文件,當第二次導入時,就會直接加載 .pyc 文件,而不會再次執行模塊代碼。因此,我們只需把相關的函數和數據定義在一個模塊中,就可以獲得一個單例對象了。如果我們真的想要一個單例類,可以考慮這樣做:
singleton_by_module.py
class Singleton(object):def foo(self):pass singleton = Singleton()將上面的代碼保存在文件singleton_by_module.py中,要使用時,直接在其他文件中導入此文件中的對象,這個對象即是單例模式的對象test_singleton_by_module.py
from singleton_by_module import Singletont = Singleton()這樣我們一旦調用到singleton_by_module.py就會產生一個singleton_by_module.pyc,以后我們每次調用都會直接引用這里面的代碼。
2.1.2.使用裝飾器
singleton_by_decorator.py
def Singleton(cls):_instance = {}count = 0def _singleton(*args, **kargs):nonlocal countif cls not in _instance:print(f"count: {count}: {cls.__name__} not init")_instance[cls] = cls(*args, **kargs)else:print(f"count: {count}: {cls.__name__} alreay init")count+=1return _instance[cls]return _singleton@Singleton class A(object):a = 1def __init__(self, x=0):self.x = xa1 = A(2) a2 = A(3)print(f"a1 id: {id(a1)}, a1 value: {a1.x}") print(f"a2 id: {id(a2)}, a2 value: {a2.x}")### output count: 0: A not init count: 1: A alreay init a1 id: 140536039677232, a1 value: 2 a2 id: 140536039677232, a2 value: 2根據上面的運行情況,我們可以發現,當a1被創建后調用的是正常的產生實例的過程,當a2被創建的時候,由于之前實例已經被存儲下來,所以直接引用了a1的實例,所以他們的id是一樣的,也就是他們引用了同一個內存實例。
2.1.3.使用類
singleton_by_class.py
class Singleton:def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancea1 = Singleton.instance() a2 = Singleton.instance()print(f"a1 id: {id(a1)}") print(f"a2 id: {id(a2)}")### output a1 id: 140419818871776 a2 id: 140419818871776一般情況,大家以為這樣就完成了單例模式,但是這樣當使用多線程時會存在問題
singleton_by_class_mutli_threading.py
class Singleton(object):def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instanceimport threadingdef task(arg):obj = Singleton.instance()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()程序執行后,打印結果如下:
<__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0> <__main__.Singleton object at 0x02C933D0>看起來也沒有問題,那是因為執行速度過快,如果在init方法中有一些IO操作,就會發現問題了,下面我們通過time.sleep模擬
我們在上面init方法中加入以下代碼:
singleton_by_class_mutli_threading_sleep.py
def __init__(self):import timetime.sleep(1)重新執行程序后,結果如下
<__main__.Singleton object at 0x034A3410> <__main__.Singleton object at 0x034BB990> <__main__.Singleton object at 0x034BB910> <__main__.Singleton object at 0x034ADED0> <__main__.Singleton object at 0x034E6BD0> <__main__.Singleton object at 0x034E6C10> <__main__.Singleton object at 0x034E6B90> <__main__.Singleton object at 0x034BBA30> <__main__.Singleton object at 0x034F6B90> <__main__.Singleton object at 0x034E6A90>問題出現了!按照以上方式創建的單例,無法支持多線程
解決辦法:加鎖!未加鎖部分并發執行,加鎖部分串行執行,速度降低,但是保證了數據安全
singleton_by_class_mutli_threading_lock.py
import time import threading class Singleton:_instance_lock = threading.Lock()def __init__(self):time.sleep(1)@classmethoddef instance(cls, *args, **kwargs):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancedef task(arg):obj = Singleton.instance()print(obj) for i in range(10):t = threading.Thread(target=task,args=[i,])t.start() time.sleep(20) obj = Singleton.instance() print(obj)打印結果如下:
<__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110> <__main__.Singleton object at 0x02D6B110>這樣就差不多了,但是還是有一點小問題,就是當程序執行時,執行了time.sleep(20)后,下面實例化對象時,此時已經是單例模式了,但我們還是加了鎖,這樣不太好,再進行一些優化,把intance方法,改成下面的這樣就行:
@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instance這樣,一個可以支持多線程的單例模式就完成了
singleton_by_class_mutli_threading_safe.py
import time import threading class Singleton:_instance_lock = threading.Lock()def __init__(self):time.sleep(1)@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancedef task(arg):obj = Singleton.instance()print(obj) for i in range(10):t = threading.Thread(target=task,args=[i,])t.start() time.sleep(20) obj = Singleton.instance() print(obj)完整代碼這種方式實現的單例模式,使用時會有限制,以后實例化必須通過 obj = Singleton.instance()
如果用 obj=Singleton() ,這種方式得到的不是單例
2.1.4基于new方法實現(推薦使用,方便)
通過上面例子,我們可以知道,當我們實現單例時,為了保證線程安全需要在內部加入鎖
我們知道,當我們實例化一個對象時,是先執行了類的new方法(我們沒寫時,默認調用type.new),實例化對象;然后再執行類的init方法,對這個對象進行初始化,所有我們可以基于這個,實現單例模式
singleton_by_new.py
import threading class Singleton:_instance_lock = threading.Lock()def __init__(self):passdef __new__(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = super(Singleton,cls).__new__(cls,*args, **kwargs)return Singleton._instanceobj1 = Singleton() obj2 = Singleton() print(obj1,obj2)def task(arg):obj = Singleton()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()打印結果如下:
<__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0>采用這種方式的單例模式,以后實例化對象時,和平時實例化對象的方法一樣 obj = Singleton()
2.1.5.基于metaclass方式實現
相關知識
""" 1.類由type創建,創建類時,type的init方法自動執行,類() 執行type的call方法(類的new方法,類的init方法) 2.對象由類創建,創建對象時,類的init方法自動執行,對象()執行類的call方法 """class Foo:def __init__(self):passdef __call__(self, *args, **kwargs):passobj = Foo() # 執行type的 __call__ 方法,調用 Foo類(是type的對象)的 __new__方法,用于創建對象,然后調用 Foo類(是type的對象)的 __init__方法,用于對對象初始化。obj() # 執行Foo的 __call__ 方法元類的使用metaclass_ex.py
class SingletonType(type):def __init__(self,*args,**kwargs):super(SingletonType,self).__init__(*args,**kwargs)def __call__(cls, *args, **kwargs): # 這里的cls,即Foo類print('cls',cls)obj = cls.__new__(cls,*args, **kwargs)cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj)return objclass Foo(metaclass=SingletonType): # 指定創建Foo的type為SingletonTypedef __init__(self,name):self.name = namedef __new__(cls, *args, **kwargs):return object.__new__(cls)obj = Foo('xx')實現單例模式
singleton_by_metaclass.py
import threadingclass SingletonType(type):_instance_lock = threading.Lock()def __call__(cls, *args, **kwargs):if not hasattr(cls, "_instance"):with SingletonType._instance_lock:if not hasattr(cls, "_instance"):cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)return cls._instanceclass Foo(metaclass=SingletonType):def __init__(self,name):self.name = nameobj1 = Foo('name') obj2 = Foo('name') print(obj1,obj2)2.2 實例分析
總線是計算機各種功能部件或者設備之間傳送數據、控制信號等信息的公共通信解決方案之一。現假設有如下場景:某中央處理器(CPU)通過某種協議總線與一個信號燈相連,信號燈有64種顏色可以設置,中央處理器上運行著三個線程,都可以對這個信號燈進行控制,并且可以獨立設置該信號燈的顏色。抽象掉協議細節(用打印表示),如何實現線程對信號等的控制邏輯。 加線程鎖進行控制,無疑是最先想到的方法,但各個線程對鎖的控制,無疑加大了模塊之間的耦合。下面,我們就用設計模式中的單例模式,來解決這個問題。
代碼如下:
import threading import time #這里使用方法__new__來實現單例模式 class Singleton(object):#抽象單例def __new__(cls, *args, **kw):if not hasattr(cls, '_instance'):orig = super(Singleton, cls)cls._instance = orig.__new__(cls, *args, **kw)return cls._instance #總線 class Bus(Singleton):lock = threading.RLock()def sendData(self,data):self.lock.acquire()time.sleep(3)print "Sending Signal Data...",dataself.lock.release() #線程對象,為更加說明單例的含義,這里將Bus對象實例化寫在了run里 class VisitEntity(threading.Thread):my_bus=""name=""def getName(self):return self.namedef setName(self, name):self.name=namedef run(self):self.my_bus=Bus()self.my_bus.sendData(self.name)if __name__=="__main__":for i in range(3):print "Entity %d begin to run..."%imy_entity=VisitEntity()my_entity.setName("Entity_"+str(i))my_entity.start()運行結果如下: Entity 0 begin to run... Entity 1 begin to run... Entity 2 begin to run... Sending Signal Data... Entity_0 Sending Signal Data... Entity_1 Sending Signal Data... Entity_2 在程序運行過程中,三個線程同時運行(運行結果的前三行先很快打印出來),而后分別占用總線資源(后三行每隔3秒打印一行)。雖然看上去總線Bus被實例化了三次,但實際上在內存里只有一個實例。
總結
因為單例模式在設計模式中算是最基礎且最簡單的一個模式,因此在一般初級面試的時候,面試官都會通過這個問題來考察,一個很重要的原因是單例模式實現方法多種且優化的方式也有很多,所以也很能考察應聘者的水平,所以,大家要好好學這個最基礎的設計模式啊!另外,在Java中單例模式常說的飽漢餓漢模式,其實和Python中的利用__new__和利用class來創建是一樣的,也就是在什么時候創建實例的區別。
總結
以上是生活随笔為你收集整理的print python 如何加锁_深度解密Python单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 贴片铝电容识别及型号_贴片钽电容封装及规
- 下一篇: ios 设置按钮不可见_iOS的五大设计