go的单例模式
<!-- more -->
var instance *singletype single struct {
Name string
}
func GetInstance() *single {
if instance == nil {
instance = &single{}
}
return instance
}
func main() {
i := GetInstance()
i.Name = "1"
i1 := GetInstance()
i1.Name = "1"
fmt.Printf("%p
", i) // 0xc000094040
fmt.Printf("%p
", i1) // 0xc000094040
}
从上面例子打印结果可以看出对象指向的地址都是一样的,所以判断i和i1是同一个对象。有个需要注意的地方single是小写的,如果是大写包外可见,就可以实例化single对象,不符合单例模式只有一个实例的特点。
但是这么写单协程是没问题的,多协程就会出问题。于是利用go的同步原语sync来加锁。
var instance *singlevar lock sync.Mutex
type single struct {
Name string
}
func GetInstance() *single {
lock.Lock()
defer lock.Unlock()
if instance == nil {
instance = &single{}
}
return instance
}
// 上面每次获取实例都会加锁,优化下,就是我们常见的双重检查机制,只有在没有实例化的情况下再加锁
func GetInstance() *single {
if instance == nil {
lock.Lock()
defer lock.Unlock()
if instance == nil {
instance = &single{}
}
}
return instance
}
上面利用加锁来达到协程安全的单例模式,利用sync.Mutex同步原语实际上是利用临界区的方式,go还有利用sync.Once实现单例:
var instance *singlevar once sync.Once
type single struct {
Name string
}
func GetInstance() *single {
once.Do(func() {
instance = &single{}
})
return instance
}
至此我们单例模式就讲完了,现在再讲sync.Once, Once实际上是只计算Do方法调用的次数,并且Do内的函数只执行一次。那么Once是怎么做到的呢,源码很简单:
type Once struct { done uint32
m Mutex
}
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()
}
}
sync.Once是一个结构体,内部定义了一个锁和一个类似标志的done,通过对done值的计算,判断done为0时sync.Once的Do的函数才执行一次,否则不执行。
我们看到Do方法中利用atomic.LoadInt32接受一个*int32类型的指针值,并会返回该指针值指向的那个值,atomic是利用CAS并不是加锁,这种读多的场景利用CAS的方式也是性能上的优化。而在doSlow利用了atomic将done+1,但是CAS在频繁写的场景中并不是总是能成功,所以这里为了保险起见还是加了锁。
从几种单例模式讲到sync.Once,总体来说利用sync.Once更优雅,性能上也更好些。
以上是 go的单例模式 的全部内容, 来源链接: utcz.com/z/513925.html