This article mainly introduces the implementation of the single-instance mode in the Python Design Mode and Its Application in Tornado, and explains the problems related to the thread control used in the single-instance mode to design the Tornado framework, for more information, see
Implementation of Singleton Mode
Bind a class instance to a class variable
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
However, after the subclass is inherited, You can override _ new _ to lose the singleton feature.
class D(Singleton): def __new__(cls, *args): return super(D, cls).__new__(cls, *args)
Implement with a decorator
def singleton(_cls): inst = {} def getinstance(*args, **kwargs): if _cls not in inst: inst[_cls] = _cls(*args, **kwargs) return inst[_cls] return getinstance@singletonclass MyClass(object): pass
The problem is that after the decoration, the returned result is not a class but a function. Of course, you can define a class in singleton to solve the problem, but this is very troublesome.
Use _ metaclass __, which is the most recommended method
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
Use of Singleton mode in Tornado
Let's take a look at the singleton mode in tornado. IOLoop:
class IOLoop(object): @staticmethod def instance(): """Returns a global `IOLoop` instance.Most applications have a single, global `IOLoop` running on themain thread. Use this method to get this instance fromanother 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
Why is double check required? Here is a simple Singleton mode. Let's take a look at the Code:
class Singleton(object): @staticmathod def instance(): if not hasattr(Singleton, '_instance'): Singleton._instance = Singleton() return Singleton._instance
In Python, you can post in the true constructor _ 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
This situation seems to be good, but it cannot be guaranteed to be useful in a multi-threaded environment. See the figure below:
This is obviously not feasible after multithreading occurs.
1. Lock to synchronize threads
Lock code:
import threadingclass Singleton(object): _instance_lock = threading.Lock() @staticmethod def instance(): with Singleton._instance_lock: if not hasattr(Singleton, '_instance'): Singleton._instance = Singleton() return Singleton._instance
This does solve the problem of multithreading, but we only need to lock the instance during instantiation, and Singleton in other cases. _ The instance already exists and does not need to be locked. At this time, the other threads that want to obtain the Singleton instance still have to wait. The existence of the lock significantly reduces the efficiency and performance loss.
2. Global Variables
In Java/C ++, global variables can also be used to solve the above locking (synchronization) problems:
class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
In Python, this is the case:
class Singleton(object): @staticmethod def instance(): return _g_singleton_g_singleton = Singleton()# def get_instance():# return _g_singleton
However, if this class occupies a large amount of resources, it will not be cost-effective and the Python code will be ugly ......
So there is a singleton mode of double check like tornado. IOLoop. instance. In the case of multiple threads, neither the performance decrease caused by synchronization (locking) nor the resource waste caused by direct instantiation of global variables.
3. decorator
If you use a decorator, it will be like this:
import functoolsdef 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:#@singletonclass Foo: def __new__(cls): cls.x = 10 return object.__new__(cls) def __init__(self): assert self.x == 10 self.x = 15assert Foo().x == 15Foo().x = 20assert Foo().x == 20
def singleton(cls): instance = cls() instance.__call__ = lambda: instance return instance## Sample use#@singletonclass Highlander: x = 100 # Of course you can have any attributes or methods you like.Highlander() is Highlander() is Highlander #=> Trueid(Highlander()) == id(Highlander) #=> TrueHighlander().x == Highlander.x == 100 #=> TrueHighlander.x = 50Highlander().x == Highlander.x == 50 #=> True