• 设计模式之单例模式


    什么是总线

      总线(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
    
  • 相关阅读:
    DS博客作业05--树
    DS博客作业03--栈和队列
    DS博客作业02--线性表
    DS博客作业01-日期抽象数据类型设计和实现
    easyx的基础应用教程
    C语言博客作业06--结构体&文件
    python接口自动化
    fiddler的使用
    python接口自动化——初级
    python.day.10——面向对象(二)
  • 原文地址:https://www.cnblogs.com/Lance-WJ/p/13565991.html
Copyright © 2020-2023  润新知