这篇文章主要讲解了“go单例怎么实现双重检测是否安全”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go单例怎么实现双重检测是否安全”吧!
当前有的项目直接使用Mutex锁,有的就直接判断nil则创建,对于前者,每次都加锁性能差,对于后者则会出现多个实例,也就不是单例了
进而想要改进一下,在这不讨论饿汉和线程非安全的实现,对于go中线程安全的懒汉实现,常见两种:
双重检验sync.Once
package main import ( "sync" "testing" ) var ( instance *int lock sync.Mutex func getInstance() *int { if instance == nil { lock.Lock() defer lock.Unlock() if instance == nil { i := 1 instance = &i } } return instance } // 用于下边基准测试 func BenchmarkSprintf(b *testing.B){ for i:=0;i<b.N;i++{ go getInstance()
基于java中双重检验锁的经验,因为jvm的内存模型,双重检验锁会出现可见性问题,可以通过 volatile解决
那么在go里会有类似问题吗?
关键点在于instance变量的读和写是否是原子操作
这里做了个race竞态检测:
可以看到20行的写入和14行的读取发生了竞态
上例中用64位(系统是64位)的int指针表示一个实例,也说明了对于64位数据的写入和读取是非原子操作
我们看另一种实现:sync.Once方法
package main import ( "sync" "testing" ) var ( instance *int once sync.Once func getInstance() *int { once.Do(func(){ if instance == nil { i := 1 instance = &i } }) return instance } func BenchmarkSprintf(b *testing.B){ for i:=0;i<b.N;i++{ go getInstance() }
实现比双重检验看起来要整洁许多
race检测结果:
没有发生竞态
那么sync.Once是怎么实现的呢
看下源码:
package sync import ( "sync/atomic" ) 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字段)的读和写使用了atomic包的StoreUint32和LoadUint32方法
sync.Once使用一个32位无符号整数表示共享变量,即使是32位变量的读写操作都需要atomic包方法来实现原子性,更说明了go里边指针的读写不能保证原子性
感谢各位的阅读,以上就是“go单例怎么实现双重检测是否安全”的内容了,经过本文的学习后,相信大家对go单例怎么实现双重检测是否安全这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。