深度解密Python单例模式
- 认识单例模式
认识单例模式
1 单例模式含义
2 单例模式优点
3 单例模式缺点
4 单例模式应用
- Python实现单例模式
Python实现单例模式
1 多种实现方法
2 实例分析
- 总结
总结
认识单例模式
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 Singleton
t = Singleton()
这样我们一旦调用到singleton_by_module.py就会产生一个singleton_by_module.pyc,以后我们每次调用都会直接引用这里面的代码。
2.1.2.使用装饰器
singleton_by_decorator.py
def Singleton(cls):
_instance = {} count = 0 def _singleton(*args, **kargs): nonlocal count if 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+=1 return _instance[cls] return _singleton
@Singleton
class A(object):
a = 1 def __init__(self, x=0): self.x = x
a1 = 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 @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance
a1 = 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 @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance
import threading
def 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 time time.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) @classmethod def instance(cls, *args, **kwargs): with Singleton._instance_lock: if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance
def 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方法,改成下面的这样就行:
@classmethod
def 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) @classmethod def 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
def 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): pass
def __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._instance
obj1 = 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): pass def __call__(self, *args, **kwargs): pass
obj = 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 obj
class Foo(metaclass=SingletonType): # 指定创建Foo的type为SingletonType
def __init__(self,name): self.name = name def __new__(cls, *args, **kwargs): return object.__new__(cls)
obj = Foo('xx')
实现单例模式
singleton_by_metaclass.py
import threading
class 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._instance
class Foo(metaclass=SingletonType):
def __init__(self,name): self.name = name
obj1 = 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...",data self.lock.release()
线程对象,为更加说明单例的含义,这里将Bus对象实例化写在了run里
class VisitEntity(threading.Thread):
my_bus="" name="" def getName(self): return self.name def setName(self, name): self.name=name def 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..."%i my_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来创建是一样的,也就是在什么时候创建实例的区别。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
深度解密Python工厂类相关模式
认识工厂类相关模式认识工厂类相关模式 1 工厂类相关模式含义工厂方法模式(英语:Factory method pattern)是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”[1]创建一个对象常常需要复杂的过程,所以不适合包含在一个复合对象中。创建对象可能会导致大量的重复代码,可能会需要复合对象访问不到的信息,也可能提供不了足够级别的抽象,还可能并不是复合对象概念的一部分。工厂方法模式通过定义一个单独的创建对象的方法来解决这些问题。由子类实现这个方法来创建具体类型的对象。对象创建中的有些过程包括决定创建哪个对象、管理对象的生命周期,以及管理特定对象的创建和销毁的概念。2 工厂类相关模式优点工厂模式、抽象工厂模式的优点:1、工厂模式巨有非常好的封装性,代码结构清晰;在抽象工厂模式中,其结构还可以随着需要进行更深或者更浅的抽象层级调整,非常灵活;2、屏蔽产品类,使产品的被使用业务场景和产品的功...
- 下一篇
深入理解Python的TLS机制和Threading.local()
1.背景介绍我之前写过一个关于Python的TLS机制的浅浅析,大家可以参考这个文章,首先,我们再来熟悉熟悉什么是TLS机制。 1.1 Thread Local Storage(线程局部存储)这个概念最早是相对于全局变量来说的,就是我们在编程的时候,会涉及到希望所有线程都能够共享访问同一个变量,在 Python/Go/C 中,我们就可以定义一个全局变量,这样Global Variable 对多个线程就是可见的,因为同一个进程所有线程共享地址空间,大家都可以操作。例如,一个全局的配置变量或单实例对象,所有线程就可以很方便访问了,但是仅仅这样有一个前提,就是这个变量的并发操作必须是幂等的,读写不影响我们程序的正确性。但是往往多线程共同操作一个全局变量,就会影响程序的正确性,因此我们必须枷锁,比如经典的并发加操作。 import threadingcount = 0lock = threading.RLock()def inc(): global count, lock with lock: count += 1 上面那个例子很多博客用来做ThreadLocal变量的讲解,实际上我觉得是有误...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题