Python设计模式中单例模式的实现及在Tornado中的应用

938次阅读  |  发布于5年以前

单例模式的实现方式
将类实例绑定到类变量上


    class Singleton(object):
      _instance = None

      def __new__(cls, *args):
        if not isinstance(cls._instance, cls):
          cls._instance = super(Singleton, cls).__new__(cls, *args)
        return cls._instance

但是子类在继承后可以重写new以失去单例特性


    class D(Singleton):

      def __new__(cls, *args):
        return super(D, cls).__new__(cls, *args)

使用装饰器实现


    def singleton(_cls):
      inst = {}

      def getinstance(*args, **kwargs):
        if _cls not in inst:
          inst[_cls] = _cls(*args, **kwargs)
        return inst[_cls]
      return getinstance

    @singleton
    class MyClass(object):
      pass

问题是这样装饰以后返回的不是类而是函数,当然你可以singleton里定义一个类来解决问题,但这样就显得很麻烦了

使用metaclass,这个方式最推荐


    class Singleton(type):
      _inst = {}

      def __call__(cls, *args, **kwargs):
        if cls not in cls._inst:
          cls._inst[cls] = super(Singleton, cls).__call__(*args)
        return cls._inst[cls]


    class MyClass(object):
      __metaclass__ = Singleton

Tornado中的单例模式运用
来看看tornado.IOLoop中的单例模式:


    class IOLoop(object):

      @staticmethod
      def instance():
        """Returns a global `IOLoop` instance.

    Most applications have a single, global `IOLoop` running on the
    main thread. Use this method to get this instance from
    another thread. To get the current thread's `IOLoop`, use `current()`.
    """
        if not hasattr(IOLoop, "_instance"):
          with IOLoop._instance_lock:
            if not hasattr(IOLoop, "_instance"):
              # New instance after double check
              IOLoop._instance = IOLoop()
        return IOLoop._instance

为什么这里要double check?来看个这里面简单的单例模式,先来看看代码:


    class Singleton(object):

      @staticmathod
      def instance():
        if not hasattr(Singleton, '_instance'):
          Singleton._instance = Singleton()
        return Singleton._instance

在 Python 里,可以在真正的构造函数new里做文章:


    class Singleton(object):

      def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
          cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

这种情况看似还不错,但是不能保证在多线程的环境下仍然好用,看图:

201632180733229.png \(683×463\)

出现了多线程之后,这明显就是行不通的。

1.上锁使线程同步
上锁后的代码:


    import threading

    class Singleton(object):

      _instance_lock = threading.Lock()

      @staticmethod
      def instance():
        with Singleton._instance_lock:
          if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton()
        return Singleton._instance

这里确实是解决了多线程的情况,但是我们只有实例化的时候需要上锁,其它时候Singleton._instance已经存在了,不需要锁了,但是这时候其它要获得Singleton实例的线程还是必须等待,锁的存在明显降低了效率,有性能损耗。

2.全局变量
在 Java/C++ 这些语言里还可以利用全局变量的方式解决上面那种加锁(同步)带来的问题:


    class Singleton {

      private static Singleton instance = new Singleton();

      private Singleton() {}

      public static Singleton getInstance() {
        return instance;
      }

    }

在 Python 里就是这样了:


    class Singleton(object):

      @staticmethod
      def instance():
        return _g_singleton

    _g_singleton = Singleton()

    # def get_instance():
    # return _g_singleton

但是如果这个类所占的资源较多的话,还没有用这个实例就已经存在了,是非常不划算的,Python 代码也略显丑陋……

所以出现了像tornado.IOLoop.instance()那样的double check的单例模式了。在多线程的情况下,既没有同步(加锁)带来的性能下降,也没有全局变量直接实例化带来的资源浪费。

3.装饰器

如果使用装饰器,那么将会是这样:


    import functools

    def singleton(cls):
      ''' Use class as singleton. '''

      cls.__new_original__ = cls.__new__

      @functools.wraps(cls.__new__)
      def singleton_new(cls, *args, **kw):
        it = cls.__dict__.get('__it__')
        if it is not None:
          return it

        cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
        it.__init_original__(*args, **kw)
        return it

      cls.__new__ = singleton_new
      cls.__init_original__ = cls.__init__
      cls.__init__ = object.__init__

      return cls

    #
    # Sample use:
    #

    @singleton
    class Foo:
      def __new__(cls):
        cls.x = 10
        return object.__new__(cls)

      def __init__(self):
        assert self.x == 10
        self.x = 15

    assert Foo().x == 15
    Foo().x = 20
    assert Foo().x == 20


    def singleton(cls):
      instance = cls()
      instance.__call__ = lambda: instance
      return instance

    #
    # Sample use
    #

    @singleton
    class Highlander:
      x = 100
      # Of course you can have any attributes or methods you like.

    Highlander() is Highlander() is Highlander #=> True
    id(Highlander()) == id(Highlander) #=> True
    Highlander().x == Highlander.x == 100 #=> True
    Highlander.x = 50
    Highlander().x == Highlander.x == 50 #=> True

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8