适用场景
一些实例化很耗费资源的类, 比如线程池, 协程池
一些只允许存在一个实例的类, 比如日志管理模块
实现方式
饿汉式
一开始就完成单例的初始化
实现流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var s *demo
func init() { s = newDemo() }
type demo struct { } func newDemo() *demo { return &demo{} } func (s *demo)Work { }
func GetDemo() *demo { return s }
|
代码规范性讨论
- demo是包内不可导出的类型, 在包内获取到了, 也不能作为入参或者出参进行传递
- demo对外暴露, 混淆了不可导出类的边界与定位
处理方法
在不可导出单例类基础上 包上一层interface, 然后将这个inerface作为返回参数类型
1 2 3 4 5 6
| type Instace interface { Work() } func GetInstance() Instance { return s }
|
懒汉式
要用的时候再初始化
实现流程
1.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var s *singleton
type singleton struct { }
func newSingleton() *singleton { return &singleton{} }
func (s *singleton) Work() { }
type Instance interface { Work() }
func GetInstance() Instance { if s == nil { s = newSingleton() } return s }
|
但是这样存在并发问题, 可能被初始化两次, 与设计违背
2.0
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
| var ( s *singleton mux sync.Mutex )
type singleton struct { }
func newSingleton() *singleton { return &singleton{} }
func (s *singleton) Work() { }
type Instance interface { Work() }
func GetInstance() Instance { mux.Lock() defer mux.Unlock() if s == nil { s = newSingleton() } return s }
|
调用Get方法的时候先拿锁, 这保证了并发安全,但是这个单例只需要初始化一次, 而每次Get都要加锁, 存在损耗
3.0
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 29 30
| var ( s *singleton mux sync.Mutex )
type singleton struct { }
func newSingleton() *singleton { return &singleton{} }
func (s *singleton) Work() { }
type Instance interface { Work() }
func GetInstance() Instance { if s != nil { return s } mux.Lock() defer mux.Unlock()
s = newSingleton()
return s }
|
为了避免每次Get都要拿锁, 在加锁前进行一次判断, 如果已经初始化了就直接返回
但是, 这样也存在漏洞, 比如还没有初始化的时候, 两个goroutine同时到达了mux.Lock()这里去拿锁, 只有一个g拿到了锁, 执行完初始化后, 另一个g抢到锁也会进行一次初始化
4.0
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 29 30 31 32 33
| var ( s *singleton mux sync.Mutex )
type singleton struct { }
func newSingleton() *singleton { return &singleton{} }
func (s *singleton) Work() { }
type Instance interface { Work() }
func GetInstance() Instance { if s != nil { return s } mux.Lock() defer mux.Unlock() if s != nil { return s } s = newSingleton()
return s }
|
进行一次 double check, 在加锁前进行一次检查, 看初始化状态, 如果初始化后就直接返回, 保证最大程度无锁
加锁后, 再进行一次check, 确保此时的单例仍然没有初始化过
5.0
实际上go的标准库已经有了sync.Once, 里面已经做了这些工作来保证单例
1 2 3 4 5 6
| func GetInstance() Instance { once.Do(func() { s = newSingleton() }) return s }
|
对比
饿汉式在程序运行之处就完成初始化, 有可能这个单例对象一直用不到, 造成无谓的损耗
懒汉式在首次使用才进行初始化, 但是有可能我们的实例初始化的时候存在panic, fatal, 而这要在运行的时候才会直接暴露, 有可能造成更大的伤害
Sync.Once的实现
1 2 3 4 5 6
| type Once struct { done uint32 m Mutex }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { o.doSlow(f) } }
func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }
|