muduo库-Singleton类
muduo库-Singleton类
Singleton类的实现
常见的单例模式实现主要分为以下几种:
懒汉式
懒汉式要求先声明单例对象,然后在调用时才完成实例化操作。
1 |
|
这种写法并没有考虑到多线程的情况,因此在多线程情况下可能产生多个实例对象,违背单例原则。
双检锁
为了解决 “懒汉式” 中存在的多线程问题,我们可以通过互斥锁来避免多个实例的创建。
1 |
|
进行两次判断以避免多次加锁和解锁操作,保证线程安全。这两次判空的意义如下:
- 第一层判空是为了提高效率,即当有一个线程 new 出来对象后,第二个线程就不用竞争第一个线程的对象锁而进行等待;
- 第二层判空是为了保证线程安全,防止多次实例化操作;
但是,如果该单例对象比较大,那么加锁操作就会成为一个性能瓶颈。
饿汉式
为了解决双检锁所存在的性能瓶颈问题,设计出了 “饿汉式” 的单例模式。饿汉式则要求单例对象的声明和实例化同时完成。
1 |
|
因为单例对象的静态初始化是在程序开始之前,在静态资源区中已经初始化了实例对象,所以静态初始化也就保证了线程安全性。在性能要求较高时,可以采用这种方式,从而避免了频繁的加锁、解锁操作造成的资源浪费。
静态内部类
但是如果单例对象无需考虑销毁操作,单例对象的生命周期伴随着整个程序的生命周期,程序结束时,由操作系统自动回收资源。那么如果无需考虑销毁操作,则可以用静态内部类的方式进行实现:
1 |
|
muduo库的Singleton类实现
其类图如下图所示:
在 muduo 的实现中,模板类 Singleton
的内部定义有两个静态成员变量:ponce_
和
value_
。其中,前者是 pthread_once_t
类型,以保证单例且线程安全。后者则是一个指针类型,指向单例对象。它们的初始化操作如下:
1 |
|
类中的获取实例及初始化函数如下:
1 |
|
其中,pthread_once()
保证 init()
函数只调用一次,避免多线程竞争,保证了线程安全。而
has_no_destroy<T>
则是为了判断该类型是否含有
no_destory()
函数,如果不存在,则调用
atexit()
注册 destroy()
函数,当程序正常终结时,调用指定的 destroy()
函数以回收资源。
其中的 has_no_destory<T>
定义如下,这是利用了 C++
中的 SFINEA(Substitution failure is not an error)
机制,即
“匹配失败不是错误”。具体来说,就是当重载的模板参数展开时,如果展开导致一些类型不匹配,编译器并不报错。而正好可以利用该机制来判断类是否存在某个成员函数。
1 |
|
假如类中存在 no_destory()
函数,那么
decltype(&C::no_destory)
表达式会返回一个函数指针,该指针指向类中的 no_destory()
函数。此时 test<T>(0)
就会匹配为
char test()
函数,并返回 char
,由于
sizeof(char)
为 1
,所以
sizeof(test<T>(0) == 1
表达式会返回
true
,表示存在该函数。
假如类中不存在该函数,那么在匹配函数 char test()
时就会匹配错误,进而选择次一级的匹配选项,即
int32_t test(...)
函数,由于该函数参数中为可变参数,所以可以接受任意类型的函数参数,test<T>(0)
与该函数匹配成功,进而返回 int32_t
。最后由于
sizeof(int32_t) == 1
返回
false
,则表示不存在该函数。
该类中的 destory()
函数如下:
1 |
|
其中使用 typedef
关键字定义了一个数组类型,用于在编译期判断类型 T
是否是不完全类型,不完全类型指的是只有声明却没有定义的类,那么不完全类型在
delete
操作时也就无法调用析构函数,因此在
delete value_
操作之前需要判断类型 T
是否为不完全类型。
muduo的单例模式采用模板类实现,它内部维护一个模板参数的指针,可以生成任何一个模板参数的单例。凭借SFINAE技术muduo库可以检测模板参数如果是类的话,并且该类注册了一个no_destroy()方法,那么muduo库不会去自动销毁它。否则muduo库会在init时,利用pthread_once()函数为模板参数,注册一个atexit时的destroy()垃圾回收方法,实现自动垃圾回收。智能指针也能达到类似的效果,我们平时写的单例模式在Singleton中写一个Garbage类也可以完成垃圾回收。
如果是不完全类型,那么 sizeof(T) == 0
为真,数组大小为
-1
,编译错误;如果是完全类型,那么
sizeof(T) == 0
为假,数组大小为
1
,编译成功。
总结
Singleton类的整体结构如下: