Singleton Pattern
Ensures that a class has only one instance, and provides a global access point to it.
Let’s take a look at the Singleton pattern in 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
Why double check
here?
Simple Singleton Pattern
Let’s look at the code:
class Singleton(object):
@staticmethod
def instance():
if not hasattr(Singleton, '_instance'):
Singleton._instance = Singleton()
return Singleton._instance
In Python, we can play around with the real 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 fine, but it can’t guarantee to work well in a multithreaded environment, as shown in the following figure: After introducing multithreading, it’s clear that this approach is not viable.
Locking for Thread Synchronization
The code after locking:
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
This indeed solves the multithreading situation, but we only need to lock when instantiating. At other times, Singleton._instance
already exists and doesn’t need to be locked, but other threads that want to get the Singleton
instance still have to wait. The presence of a lock clearly reduces efficiency and causes performance loss.
Global Variables
In languages like Java/C++, we can use global variables to solve the problems brought by locking (synchronization):
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
In Python, it’s like this:
class Singleton(object):
pass
singleton = Singleton() # Directly use the module-level global variable
But if the class occupies a large amount of resources, it is very uneconomical to have the instance exist before it is used…
Conclusion
So, the double check
Singleton pattern like tornado.IOLoop.instance()
appeared. In a multithreaded environment, it neither suffers from the performance degradation brought by synchronization (locking) nor wastes resources due to direct instantiation of global variables.
Supplement on June 25 (using decorator):
https://wiki.python.org/moin/PythonDecoratorLibrary#Singleton
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
https://wiki.python.org/moin/PythonDecoratorLibrary#The_Sublime_Singleton
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
Update on August 27:
import threading
class Singleton(object):
_thread_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
with cls._thread_lock:
if not hasattr(cls, '_instance'):
# cls._instance = super(Singleton, cls).__new__(cls)
cls._instance = super(Singleton, cls).__new__(
cls, *args, **kwargs)
return cls._instance
def __init__(self, bar):
self._bar = bar
def print_arg(self):
print(self._bar)
class SingletonGood(object):
_thread_lock = threading.Lock()
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
with cls._thread_lock:
if not hasattr(cls, '_instance'):
# cls._instance = super(SingletonGood, cls).__new__(cls)
# cls._instance.__init__(*args, **kwargs)
cls._instance = cls(*args, **kwargs)
return cls._instance
def __init__(self, bar):
self._bar = bar
def print_arg(self):
print(self._bar)
if __name__ == '__main__':
obj1 = Singleton('bar')
obj2 = Singleton('foobar')
assert id(obj1) == id(obj2)
obj1.print_arg() # foobar
obj2.print_arg() # foobar
print('-' * 10)
obj3 = SingletonGood.get_instance('bar')
obj4 = SingletonGood.get_instance('foobar')
assert id(obj3) == id(obj4)
obj3.print_arg() # bar
obj4.print_arg() # bar