设计模式之单例模式

什么是总线

  总线(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
posted @ 2020-08-26 16:33  Lance_王  阅读(136)  评论(0编辑  收藏  举报