返回顶部

单例模式及改进Python代码实现

一、说明

1、定义:

单例模式是所有设计模式中比较简单的一类,其定义如下:Ensure a class has only one instance, and provide a global point of access to it.(保证某一个类只有一个实例,而且在全局只有一个访问点)。

2、优缺点

优点:
1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
3、单例可长驻内存,减少系统开销。
缺点:
1、单例模式的扩展是比较困难的;
2、赋于了单例以太多的职责,某种程度上违反单一职责原则

3、场景

最常用的实例就是当一个office文档已经被打开时,通过另外一个窗口再次访问时,系统会提示已经被此文档已经被占用,只能以只读方式打开。

二、代码实现

1、方式一:常规方式实现单例模式

# 方式一
class Singleton(object):
    instance = None
    def __init__(self, name):
        self.name = name
    def __new__(cls, *args, **kwargs):  # 返回空对象
        if cls.instance:  # 是否已创建过对象
            return cls.instance  # 如果有,则直接返回
        cls.instance = object.__new__(cls)  # 没有则创建空对象,返回该空对象
        return cls.instance

obj1 = Singleton("asd")
obj2 = Singleton("a")
print(obj2.name, obj1.name)
print(id(obj1)==id(obj2))

以上为类Singleton添加关键属性instance,该属性用于如果为空表示该类还未创建实例,如果不为空,则说明已经该类已经实例化过。__new__方法会创建一个Singleton的空对象,在创建过程中添加判断类属性instance是否已绑定对象实例的逻辑。
以上代码执行结果如下:

a a
True

两个obj具有同样的name属性和地址,说明是一个对象

2、方式二:继承单例类型

为了满足自定义一些方法,扩展性更高,使用继承修改一下单例模式

# 方式二

class Singleton(object):
    instance = None
    def __init__(self, name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        # 返回空对象
        if cls.instance:
            return cls.instance
        cls.instance = object.__new__(cls)  # 创建空对象
        return cls.instance


# 继承方式改写单例模式
class MySingleton(Singleton):

    def func(self):
        pass

obj3 = MySingleton("my singleton")
obj4 = MySingleton("my singleton")
print(id(obj4)==id(obj3))  # True

3、方式三:多线程改写单例模式

以上两种方式属于虽然功能正常,但是在多线程情景下,可能存在由于单例类创建空对象的时候非原子操作,所以一个线程创建单例调用__new__方法返回空对象的过程中,其他线程也在执行,也就是出现了多个线程同时拿到了Singleton.instance=None的情形,因而返回了多个不同的新的空对象,导致单例模式失效。代码如下

import time
import threading


# 单例模式常规写法多线程下的问题
class Singleton(object):
    instance = None

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        if cls.instance:
            return cls.instance
        time.sleep(0.01)  # 放慢线程创建空对象的速度
        cls.instance = object.__new__(cls)
        return cls.instance


def task(n):
    obj = Singleton("singleton by %dth thread" % n)
    print("thread %s, id=%s" % (obj.name, id(obj)))

for i in range(10):
    t = threading.Thread(target=task, args=(i+1,))
    t.start()

以上引入threading模块创建多个线程,在__new__方法中使用time.sleep(0.01)语句阻塞单个线程创建空对象从而模拟极端情况。
执行结果如下

singleton by 1th thread, id=2475130212192
singleton by 2th thread, id=2475130212248
singleton by 4th thread, id=2474978871000
singleton by 6th thread, id=2474978871392
singleton by 5th thread, id=2475128184224
singleton by 3th thread, id=2474978871336
singleton by 7th thread, id=2474978871000
singleton by 9th thread, id=2475128184224
singleton by 10th thread, id=2474978871336
singleton by 8th thread, id=2474978871392

可看到不同的线程创建单例顺序不同,地址也不同。单例模式失效。
究其原因是因为,__new__方法可以由多个线程同时执行,所以可以使用锁机制控制。代码如下方式三

# 方式三:多线程版本下的单例模式
import time
import threading


lock = threading.RLock()


# 多线程情景下的单例模式
class Singleton(object):
    instance = None
    
    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):

        with lock:
            if cls.instance:
                return cls.instance
            time.sleep(0.1) 
            cls.instance = object.__new__(cls)  # 创建空对象
            return cls.instance


def task(n):
    obj = Singleton("%dth singleton" % n)
    print("%s, id=%s" % (obj.name, id(obj)))


for i in range(10):
    t = threading.Thread(target=task, args=(i+1,))
    t.start()

如上,采用锁机制解决问题资源同步和数据一致性的问题。当多个线程尝试创建对象的时候,都会先请求锁,拿到锁则执行,否则只能等待。其实等于变相使用锁机制控制多线程对同一资源instance类变量的访问。
代码结果如下:

singleton by 1th thread, id=2090604208704
singleton by 2th thread, id=2090604208704
singleton by 3th thread, id=2090604208704
singleton by 4th thread, id=2090604208704
singleton by 5th thread, id=2090604208704
singleton by 6th thread, id=2090604208704
singleton by 7th thread, id=2090604208704
singleton by 8th thread, id=2090604208704
singleton by 9th thread, id=2090604208704
singleton by 10th thread, id=2090604208704

以上使用线程锁,采用with上下文管理管理的方式,同样可以使用手动获取锁,释放的方式实现如下。

lock.acquire()
if cls.instance:
    return cls.instance
time.sleep(0.1) 
cls.instance = object.__new__(cls)  # 创建空对象
return cls.instance
lock.release()

Python中的线程锁:

  • RLock:threading.RLock(),支持锁的嵌套。
  • Lock:threading.RLock(),不支持锁的嵌套,效率比RLock稍微高一些。

如果一个函数内部操作用了Lock,那么再调用该函数就无法再上锁了,因此虽然RLock资源消耗稍微高一点,但是用Rlock多一些。

4、方式四:优化后多线程下的单例模式

# 方式四:优化后多线程下的单例模式
class Singleton(object):
    instance = None
    lock = threading.RLock()

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        # 性能优化一点
        if cls.instance:
            return cls.instance

        with cls.lock:
            if cls.instance:
                return cls.instance
            cls.instance = object.__new__(cls)
            return cls.instance

以上方式四与方式三改进的两点在于:
一:lock锁应该作为单例类型的类属性,因此将lock = threading.RLock()放到了类中。
二:在__new__方法中添加if cls.instance: return cls.instance使得后续再重新创建实例如new_obj=Singleton("new obj")的时候,不需要再进入with cls.lock:代码逻辑中耗费一次锁资源。

posted on 2021-12-17 17:18  weilanhanf  阅读(106)  评论(0编辑  收藏  举报

导航