前言
单例模式保证了在程序的不同位置都可以且仅可以取到同一个对象实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。因为单例是一个类,所以你也可以为其提供相应的操作方法,以便于对这个实例进行管理。
应用场景
- Python的logger就是一个单例模式,用以日志记录
- Windows的资源管理器是一个单例模式
- 线程池,数据库连接池等资源池一般也用单例模式
- 网站计数器
实现方式
- 使用函数装饰器实现单例
- 使用类装饰器实现单例
- 使用 new 关键字实现单例
- 使用 metaclass 实现单例
函数装饰器实现单例
python
def singleton(cls):
_instance = {}
def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner
@singleton
class Cls(object):
def __init__(self):
pass
cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))
类装饰器实现单例
python
class Singleton(object):
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]
@Singleton
class Cls2(object):
def __init__(self):
pass
cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))
New、Metaclass 关键字实现单例
python的单例模式__new__()在__init__()之前被调用,用于生产实例对象。
python
class Single(object):
_instance = None
def __new__(cls, *args, **kw): # 这里不能使用__init__,因为__init__是在instance已经生成以后才去调用的
if cls._instance is None:
cls._instance = super(Single, cls).__new__(cls, *args, **kw)
return cls._instance
def __init__(self):
pass
single1 = Single()
single2 = Single()
print(id(single1) == id(single2))
实例
python
class Singleton(object):
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super(
Singleton, cls).__new__(cls, *args, **kwargs)
return cls.__instance
def __init__(self, status_number):
self.status_number = status_number
s1 = Singleton(2)
s2 = Singleton(5)
print s1
print s2
print s1.status_number
print s2.status_number
这里我们使用了_init_方法,下面是打印结果,可以看出确实是只有一个实例,共享了实例的变量
python
<__main__.Singleton object at 0x7f5116865490>
<__main__.Singleton object at 0x7f5116865490>
5
5
不过这个例子中有一个问题我们没有解决,那就是多线程的问题,**当有多个线程同时去初始化对象时,就很可能同时判断__instance is None,从而进入初始化instance的代码中。**所以为了解决这个问题,我们必须通过同步锁来解决这个问题。
多线程安全单例模式
python
# -*- coding: utf-8 -*-
# from MyThread import *
import threading
from threading import Thread
Lock = threading.Lock()
class Singleton(object):
# 定义静态变量实例
__instance = None
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
if not cls.__instance:
try:
Lock.acquire()
# double check
if not cls.__instance:
cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
finally:
Lock.release()
return cls.__instance
def test_singleton_in_thread():
print (id(Singleton()))
if __name__ == "__main__":
idx = 0
while 1:
Thread(target=test_singleton_in_thread).start()
idx += 1
if idx > 0X100:
break
官方更简单的多线程安全实现
python
import threading
def singleton(cls):
instance = cls()
instance.__call__ = lambda: instance
return instance
@singleton
class Highlander:
x = 100
# Of course you can have any attributes or methods you like.
def worker():
hl = Highlander
hl.x += 1
print (hl)
print (hl.x)
def main():
threads = []
for _ in range(50):
t = threading.Thread(target=worker)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
if __name__ == '__main__':
main()
这样我们确实是解决了多线程数据安全的问题。但是这时候如其它线程要获得Singleton实例的线程还是必须等待,锁的存在降低了效率,有性能损耗。
总结, 我自己会有选择性的使用单例模式,把单例模式封装成一个装饰器使用起来很方便。另外对于数据库,尤其是mysql,建议不要使用单例模式。
metaclass 实现单例
在实现单例之前,需要了解使用 type 创造类的方法,代码如下:
python
def func(self):
print("do sth")
Klass = type("Klass", (), {"func": func})
c = Klass()
c.func()
我们使用 type 创造了一个类出来。这里的知识是 mataclass 实现单例的基础。
python
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Cls4(metaclass=Singleton):
pass
cls1 = Cls4()
cls2 = Cls4()
print(id(cls1) == id(cls2))
跨文件全局变量的使用场景(非单例模式)
单独建立一个文件去保存全局变量来实现
global_var.py
python
# -*- coding: utf-8 -*-
def _init(): # 初始化
global _global_dict
_global_dict = {}
def set_value(key, value):
""" 定义一个全局变量 """
_global_dict[key] = value
def get_value(key, defValue=None):
"""
获得一个全局变量,不存在则返回默认值
"""
return _global_dict.get(key, defValue)
python
import time
import global_var as glo
glo._init()
glo.set_value('start', True)
import slave.py
if __name__ == '__main__':
while 1:
if glo.get_value('start'):
print('TTTTTT')
else:
print('FFFFFF')
time.sleep(1)
python
import threading
import time
import global_var as glo
def fun():
while 1:
glo.set_value('start', False)
print(glo.get_value('start'))
time.sleep(2)
glo.set_value('start', True)
print(glo.get_value('start'))
time.sleep(2)
t = threading.Thread(target=fun) # 必要
t.start()
执行python master.py
跨文件全局变量的使用场景(单例模式)
python
class Singleton:
def __init__(self):
self.start = False
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kwargs)
return cls._instance
python
import time
from singleton import Singleton
import slave
if __name__ == '__main__':
single = Singleton()
while 1:
if single.start:
print('TTTTTT')
else:
print('FFFFFF')
time.sleep(1)
python
import time
from singleton import Singleton
import threading
def fun():
single = Singleton()
while 1:
single.start = True
print(single.start)
time.sleep(2)
single.start = False
print(single.start)
time.sleep(2)
t = threading.Thread(target=fun)
t.start()
内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。 适用场景:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时 优点: 对唯一实例的受控访问 单例相当于全局变量,但防止了命名空间被污染 与单例模式功能相似的概念:全局变量、静态变量(方法)
参考链接:
Python单例模式(Singleton)的N种实现 - 知乎
https://www.cnblogs.com/shenbuer/p/7724091.html
python单例模式及使用场景(跨文件全局变量)_python3 单例变量-CSDN博客
https://www.cnblogs.com/suwings/p/6358061.html
https://www.cnblogs.com/panlq/p/12355917.html
高并发下线程安全的单例模式(最全最经典)_单例模式 并发的情况安全吗-CSDN博客
python多线程下保持单例模式的实例唯一 -- 峰云就她了