设计模式之单例模式
什么是总线
总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束, 按照计算机所传输的信息种类,计算机的总线可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。
现假设有如下场景:某中央处理器(CPU)通过某种协议总线与一个信号灯相连,信号灯有64种颜色可以设置,中央处理器上运行着三个线程,都可以对这个信号灯进行控制,并且可以独立设置该信号灯的颜色。抽象掉协议细节(用打印表示),如何实现线程对信号等的控制逻辑。
首先我们应该想到的是加线程锁进行控制,确保信号灯顺序的安全性,但是加线程锁之后很显然加大了线程之间的耦合性,所以这里我们就想到了使用单例模式。即有且只有一个实例,若之后还实例改对象的话直接取出,个人认为与缓冲机制有异曲同工之妙,代码实现如下:
from threading import Thread,RLock
import time
#这里使用方法__new__来实现单例模式
class Singleton(object):#抽象单例
#1、 用hasattr判断
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton,cls).__new__(cls, *args, **kwargs)
return cls._instance
# 2、用if判断
# _instance = None
# def __new__(cls, *args, **kwargs):
# if cls._instance is None:
# cls._instance = super(Singleton,cls).__new__(cls, *args, **kwargs)
# return cls._instance
#总线
class Bus(Singleton):
lock = RLock()
def sendData(self,data):
self.lock.acquire()
time.sleep(3)
print("Sending Signal Data...",data)
self.lock.release()
#线程对象,为更加说明单例的含义,这里将Bus对象实例化写在了run里
class VisitEntity(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()
单例模式
什么是单例模式
定义:Ensure a class has only one instance, and provide a global point of access to it.
通俗理解:保证某一个类只有一个实例,而且在全局只有一个访问点
为什么要用单例模式
优点:
1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
3、单例可长驻内存,减少系统开销。
缺点:
1、单例模式的扩展是比较困难的;
2、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到);
3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
4、单例模式在某种情况下会导致“资源瓶颈”
应用:
1、生成全局惟一的序列号;
2、访问全局复用的惟一资源,如磁盘、总线等;
3、单个对象占用的资源过多,如数据库等;
4、系统全局统一管理,如Windows下的Task Manager;
5、网站计数器。
实现单例模式的几种方式
通过模块来实现
Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc
文件,当第二次导入时,就会直接加载 .pyc
文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
通过类方法实现单例模式
class Mysql:
__instance = None
def __init__(self,ip,port):
self.ip = ip
self.port = port
@classmethod
def singleton(cls):
if cls.__instance:
return cls.__instance
cls.__instance = cls('127.0.0.1',8080)
return cls.__instance
obj1 = Mysql('111,111,111,0',8080)
obj2 = Mysql('222,222,222,0',8080)
print(obj1)
print(obj2)
obj3 = Mysql.singleton()
obj4 = Mysql.singleton()
print(obj3,obj4)
通过元类实现单例模式
class Mymeta(type):
def __init__(self,class_name,class_bases,class_dict):
self.__instance = object.__new__(self)
self.__init__(self.__instance,'127.0.0.1',8080)
def __call__(self, *args, **kwargs):
if args or kwargs:
obj = object.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj
else:return self.__instance
class Mysql(metaclass=Mymeta):
def __init__(self,ip,port):
self.ip = ip
self.port = port
obj1 = Mysql("1.1.1.1", 3306)
obj2 = Mysql("1.1.1.2", 3306)
print(obj1)
print(obj2)
obj3 = Mysql()
obj4 = Mysql()
print(obj3 is obj4)
通过装饰器实现单例模式
def single_func(func):
_instance = func('127.0.0.1',8080)
def inner(*args,**kwargs):
if args or kwargs:
res = func(*args,**kwargs)
return res
else:
return _instance
return inner
@single_func
class Mysql:
def __init__(self,ip,port):
self.ip = ip
self.port = port
obj1 = Mysql("1.1.1.1", 3306)
obj2 = Mysql("1.1.1.2", 3306)
print(obj1)
print(obj2)
obj3 = Mysql()
obj4 = Mysql()
print(obj3 is obj4)
通过__new__
方法实现
class Mysql:
__instance=None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
obj = object.__new__(cls)
cls.__instance = obj
return cls.__instance
obj1 = Mysql()
obj2 = Mysql()
print(obj1)
print(obj2)
进阶必会
本部分主要是补充介绍多线程并发情况下,多线程高并发时,如果同时有多个线程同一时刻(极端条件下)事例化对象,那么就会出现多个对象,这就不再是单例模式了。
解决这个多线程并发带来的竞争问题,第一个想到的是加互斥锁,于是我们就用互斥锁的原理来解决这个问题。
解决的关键点,无非就是将具体示例化操作的部分加一把锁,这样同时来的多个线程就需要排队。
这样一来只有第一个抢到锁的线程实例化一个对象并保存在_instance
中,同一时刻抢锁的其他线程再抢到锁后,不会进入这个判断if not cls._instance
,直接把保存在_instance
的对象返回了。这样就实现了多线程下的单例模式。
此时还有一个问题需要解决,后面所有再事例对象时都需要再次抢锁,这会大大降低执行效率。解决这个问题也很简单,直接在抢锁前,判断下是否有单例对象了,如果有就不再往下抢锁了(代码第11行判断存在的意义)。
import threading
class Student:
_instance = None # 保存单例对象
_lock = threading.RLock() # 锁
def __new__(cls, *args, **kwargs):
if cls._instance: # 如果已经有单例了就不再去抢锁,避免IO等待
return cls._instance
with cls._lock: # 使用with语法,方便抢锁释放锁
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance