go单例怎么实现双重检测是否安全

go单例怎么实现双重检测是否安全

这篇文章主要讲解了“go单例怎么实现双重检测是否安全”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go单例怎么实现双重检测是否安全”吧!

现状

当前有的项目直接使用Mutex锁,有的就直接判断nil则创建,对于前者,每次都加锁性能差,对于后者则会出现多个实例,也就不是单例了

改进

进而想要改进一下,在这不讨论饿汉和线程非安全的实现,对于go中线程安全的懒汉实现,常见两种:

双重检验sync.Once

双重检验示例:

packagemainimport("sync""testing")var(instance*intlocksync.MutexfuncgetInstance()*int{ifinstance==nil{lock.Lock()deferlock.Unlock()ifinstance==nil{i:=1instance=&i}}returninstance}//用于下边基准测试funcBenchmarkSprintf(b*testing.B){fori:=0;i<b.N;i++{gogetInstance()

是否线程安全

基于java中双重检验锁的经验,因为jvm的内存模型,双重检验锁会出现可见性问题,可以通过 volatile解决
那么在go里会有类似问题吗?
关键点在于instance变量的读和写是否是原子操作
这里做了个race竞态检测:

可以看到20行的写入和14行的读取发生了竞态
上例中用64位(系统是64位)的int指针表示一个实例,也说明了对于64位数据的写入和读取是非原子操作

我们看另一种实现:sync.Once方法

packagemainimport("sync""testing")var(instance*intoncesync.OncefuncgetInstance()*int{once.Do(func(){ifinstance==nil{i:=1instance=&i}})returninstance}funcBenchmarkSprintf(b*testing.B){fori:=0;i<b.N;i++{gogetInstance()}

实现比双重检验看起来要整洁许多

race检测结果:

没有发生竞态

关于sync.Once

那么sync.Once是怎么实现的呢

看下源码:

packagesyncimport("sync/atomic")typeOncestruct{doneuint32mMutex}func(o*Once)Do(ffunc()){ifatomic.LoadUint32(&o.done)==0{o.doSlow(f)}func(o*Once)doSlow(ffunc()){o.m.Lock()defero.m.Unlock()ifo.done==0{deferatomic.StoreUint32(&o.done,1)f()

可以看到sync.Once内部其实也是一个双重检验锁,但是对于共享变量(done字段)的读和写使用了atomic包的StoreUint32和LoadUint32方法

sync.Once使用一个32位无符号整数表示共享变量,即使是32位变量的读写操作都需要atomic包方法来实现原子性,更说明了go里边指针的读写不能保证原子性

感谢各位的阅读,以上就是“go单例怎么实现双重检测是否安全”的内容了,经过本文的学习后,相信大家对go单例怎么实现双重检测是否安全这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是恰卡编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

发布于 2022-03-09 22:49:41
收藏
分享
海报
0 条评论
20
上一篇:Vue3的组件通信方式有哪些 下一篇:PyTorch梯度下降反向传播实例分析
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码