今天给大家讲下什么是单例模式,以及在Go语言中如何用正确的姿势实现它。其实单例模式是一种在平时开发中经常用到的软件设计模式。在设计模式结构中,其核心是只包含一个被称为单例的特殊类。通过单例模式可以确保系统中一个类只有一个实例,且该实例容易被外界访问,从而方便对实例数量的控制并节约系统资源。
1. 懒汉模式懒汉模式是平时软件开发中比较常见的,也是用的最多的一种,该模式的最大缺点就是非线程安全。什么是非线程安全呢,这里就不展开讲了,后续会单独拿一期来讲什么是线程安全和非线程安全。
懒汉模式代码示例如下:
// 定义单例的结构体type Singleton struct {}// 声明一个Singleton结构体指针的变量var singleton *Singleton// 获取单例的函数,返回Singleton结构体指针类型func GetSingleton() *Singleton { // 如果为空,则创建单例 if singleton == nil { singleton = &Singleton{} } return singleton}
2. 带普通锁的单例模式上面讲的懒汉模式,其实是没有带锁的,也就是并发处理时会发生竞争,从而会导致程序出错退出。
普通锁可以解决并发处理带来的问题,这里说的普通锁其实是使用了Go的sync.Mutex,其缺点就是要人工维护锁的添加和释放,在有些时候对锁的使用不当的话,反而会带来不必要的性能消耗,事倍功半。 其工作原理类似于Linux内核的futex对象,具体实现原理这里就不详细展开讲来,sync.Mutex的原理后面也会单独用一期来讲。
带普通锁的单例模式示例代码如下:
// 定义单例的结构体type Singleton2 struct {}// 声明一个Singleton结构体指针的变量var instance *Singleton2// 声明普通锁变量,muvar mu sync.Mutex// 获取带锁的单例函数func GetInstance() *Singleton2 { // 加锁 mu.Lock() // 函数退出前解锁 defer mu.Unlock() // 如果为空,则创建单例 if instance == nil { instance = &Singleton2{} } return instance}
3. 比较优雅的单例模式以上两钟单例模式都有缺点,不是那么完美,而Golang本身sync.Once库里就已经很好的实现了单例模式。
示例代码如下:
// 创建一个结构体type Manager struct {}// 声明两个全局变量,一个是Manager结构体指针,一个是用于单例等nocevar m *Managervar once sync.Once// 创建Manager单例函数func GetManage() *Manager { // once.Do已经优雅的封装好了部分加锁的代码 once.Do(func() { m = &Manager{} }) return m}
简单来说,sync.Once表示只执行一次函数,要做到这点,需要有两个条件:
计数器,统计函数执行的次数
线程安全,保证在多个goroutine(并发)情况下,函数仍然只执行一次,这里面其实也涉及到锁
其中sync.Once实现的核心函数就是Do(),看一下Do函数的源码,如下:
// Once源码的数据结构type Once struct { m Mutex done uint32}// Do函数实现func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 1 { return } o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() }}
根据以上的源码可以看到是定义了Once结构体,其中的done成员就是用于统计函数执行的次数的,m成员是锁,保障线程安全的。
Do方法也比较简单:
首先原子操作load函数执行次数,如果已经执行过了,就return
然后lock加锁
执行函数,原子操作store函数执行次数,赋值为1
解锁unlock
如果换做是我们来写的话,一般就会先直接加锁,然后再比较函数执行的次数,而这里的源码用了原子操作,这样可以提高性能。总的来说,一些标志位可以通过原子操作来表示,从而避免加锁,提高性能。
以上完整示例代码已归档到我的github,如有需要欢迎下载学习交流:https://github.com/Scoefield/gokeyboardman/tree/main/singletonmode
本期设计模式之Golang单例模式的介绍就到这里啦,感谢您的阅读,如有疑问或意见请及时反馈给我们。
上一篇文章:
Golang中的“包”