虽然单例模式很简单,但还是有一些门道,很少有人知道。
边界情况
Python中实现单例模式的方法有很多,我以前最常用的应该是以下写法。
1
2
3
4
5
6
7
classSingleton(object):
_instance=None
def__new__(cls,*args,**kw):
ifcls._instanceisNone:
cls._instance=object.__new__(cls,*args,**kw)
returncls._instance
这种写法有两个问题。
1.单例模式对应类实例化时不能传入参数,将上述代码扩展到以下形式。
1
2
3
4
5
6
7
8
9
10
11
12
13
classSingleton(object):
_instance=None
def__new__(cls,*args,**kw):
ifcls._instanceisNone:
cls._instance=object.__new__(cls,*args,**kw)
returncls._instance
def__init(self,x,y):
self.x=x
self.y=y
s=Singleton(1,2)
此时,Typeeroror将被抛出: object.__new__() takes exactly one argument (the type to instantiate)错误
2.多线程实例化Singleton时,可能会创建多个实例,因为很有可能多线程同时判断clss._instance is None,从而进入初
代码中的始化实例。
单例基于同步锁实现
首先考虑上述实现中遇到的第二个问题。
由于在多线程条件下会出现边界条件,从而参数多个例子,因此可以使用同步锁来解决多线程冲突。
importthreading #同步锁 defsynchronous_lock(func): defwrapper(*args,**kwargs): withthreading.Lock(): returnfunc(*args,**kwargs) returnwrapper classSingleton(object): instance=None @synchronous_lock def__new__(cls,*args,**kwargs): ifcls.instanceisNone: cls.instance=object.__new__(cls,*args,**kwargs) returncls.instance
在上述代码中,通过threading.Lock()同步单例化方法,这样在面对多个线程时就不会创建多个例子,可以简单的测试一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
defworker():
s=Singleton()
print(id(s))
deftest():
task=[]
foriinrange(10):
t=threading.Thread(target=worker)
task.append(t)
foriintask:
i.start()
foriintask:
i.join()
test()
运行后,打印的单例id相同。
更优的方法
添加同步锁后,除了无法输入参数外,没有大问题,但是有更好的解决方案吗?单例模式中是否有可接受参数的实现方法?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
defsingleton(cls):
cls.__new_original__=cls.__new__
@functools.wraps(cls.__new__)
defsingleton_new(cls,*args,**kwargs):
it=cls.__dict__.get('__it__')
ifitisnotNone:
returnit
cls.__it__=it=cls.__new_original__(cls,*args,**kwargs)
it.__init_original__(*args,**kwargs)
returnit
cls.__new__=singleton_new
cls.__init_original__=cls.__init__
cls.__init__=object.__init__
returncls
@singleton
classFoo(object):
def__new__(cls,*args,**kwargs):
cls.x=10
returnobject.__new__(cls)
def__init__(self,x,y):
assertself.x==10
self.x=x
self.y=y
上述代码定义了singleton装饰器,装饰器将在预编译时执行。利用这一特性,singleton装饰器替换了原始__new__与
__init______________________________________it___属性,以此来判断
是否要创建新的例子,如果要创建,调用原来的__new____________________init__方法将参数传递给当前类,以完成订单
实例模式的目的。
这种方法允许单例类接受相应的参数,但面对多线程实例化,可能会出现多个实例。此时,可以添加线程同步锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
defsingleton(cls):
cls.__new_original__=cls.__new__
@functools.wraps(cls.__new__)
defsingleton_new(cls,*args,**kwargs):
#同步锁
withthreading.Lock():
it=cls.__dict__.get('__it__')
ifitisnotNone:
returnit
cls.__it__=it=cls.__new_original__(cls,*args,**kwargs)
it.__init_original__(*args,**kwargs)
returnit
cls.__new__=singleton_new
cls.__init_original__=cls.__init__
cls.__init__=object.__init__
returncls
额外考虑是否添加同步锁。
如果一个项目不需要使用线程相关机制,只需要在单个例子中使用线程锁,这实际上是不必要的,它会减慢项目的运行速度。
阅读与CPython线程模块相关的源代码,您会发现Python一开始并没有与初始线程相关的环境,只有当您使用theading库相关的功能时,
将PyEval_Initthreads方法初始化多线程相关环境,代码片段如下(我省略了很多无关代码)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
staticPyObject*
thread_PyThread_start_new_thread(PyObject*self,PyObject*fargs)
{
PyObject*func,*args,*keyw=NULL;
structbootstate*boot;
unsignedlongident;
//多线程环境初始化,解释器默认不初始化,只有用户使用时才初始化。
PyEval_InitThreads();/*Starttheinterpreter'sthread-awareness*/
///创建线程
ident=PyThread_start_new_thread(t_bootstrap,(void*)boot);
//返回线程id
returnPyLong_FromUnsignedLong(ident);
}
为什么会这样?
由于多线程环境将启动GIL锁相关逻辑,这将影响Python程序的运行速度。许多简单的Python程序不需要使用多线程。此时,它们不需要初始化与线程相关的环境。Python程序在没有GIL锁的情况下运行得更快。
若您的项目不涉及多线程操作,则无需使用同步锁实现单例模式。
结尾
1.互联网上有很多关于Python实现单例模式的文章。您只需要从多线程下是否能保证单例和单例化时是否能输入初始参数两点来判断
可以采用相应的实现方法。
2..光理论是不够的。顺便说一句,我给你一套2020年最新python入门到高级项目的实用视频教程。你可以去小编的python交流。裙子 :七衣九七七巴而五(数字谐音)可以在转换下找到,也可以和老司机沟通咨询!
本文的文本和图片来自互联网和他们自己的想法,仅供学习和交流,没有任何商业用途,版权属于原作者。如有任何问题,请及时联系我们处理。
有关python的更多文章,请关注python自学网。