Python单例模式的实现
前言
最近在做一个物联网项目,用户端通过Websocket与服务器进行通信,在服务器上需要将Websocket上的消息通过Mqtt转发给设备,这样就需要在服务器端建立一个Mqtt的通信线程供websocket使用。服务器我采用的是Django+channels来实现Websocket通信。与此同时,我定义了一个MqttClient类用于实现Mqtt收发等功能的实现。该类的通信线程会随Django一同启动。在Webocket收到消息时,我需要通过该进程的对象的send方法将Websocket消息转发至设备。理论来说,一个服务端进程应该保证只启动一个Mqtt的通信线程,否则会导致消息的重复处理。因此我想到了将MqttClient设计成单例模式,保证该类只实例化一个对象,保证通信线程的为唯一。
单例模式
单例模式是一种设计模式。其目的是让类创建的对象在内存中只有唯一的一个实例。在Python中我们可以通过查看类实例化的对象的内存地址,或者通过查看对象的id来区分在不同位置实例化的对象是否为同一个。
实现方法
_new_()方法
Python中,所有的类都会继承Base基类,在Base中定义了__new__()方法,该方法在类的初始化__init()__之前被解释器执行,用于为对象分配内存,返回对象引用。
因此,我们要实现单例模式的思路也就很清晰了,只需要在类分配内存时先检查类是否被实例化,再决定是否分配内存就可以了。
1 | class Singleton: |
运行后可以看到实例化的对象地址都相同,说明这些对象仅申请了一次内存。
装饰器方法
在之前的一篇文章我介绍过装饰器的相关原理,下面通过装饰器方式来实现一下单例模式。
1 | def singleton(cls): |
可以看到,使用装饰器实现单例模式与第一种方法不同,这一次类的构造函数只执行了一次。因为采用装饰器方法时,类还没构造时就已经在外部检查了类的实例化情况,所以类只在第一次进行实例化,以后只返回第一次实例化生成的对象。
线程安全
在多线程下使用单例模式时可能会出现线程安全问题。假如有两个线程,两个线程都实例化一个单例模式类的对象。我们假设一种情况,一个线程先开始实例化,在创建对象时,另一个线程此时正在判断类的实例化情况,或者极端情况下两个类同时进行实例化,那么两个线程在判断实例化情况时都认为该类没有被实例化过,因此各自创建了一个对象,那么这两个线程最后创建了两个不同的对象,单例模式也就失效了。
解决这一问题很简单,只需要加一个线程锁,只有一个线程的实例化结束后另一个线程才会开始判断实例化,这样就避免了线程不安全。