sync.Once和自己加锁有什么区别吗?
单例
func loadIcons() {
icons = map[string]string{
"left": loadIcon("left.png"),
"up": loadIcon("up.png"),
"right": loadIcon("right.png"),
"down": loadIcon("down.png"),
}
// Icon 被多个goroutine调用时不是并发安全的
//每个goroutine都满足串行一致的基础上自由地重排访问内存的顺序
func Icon(name string) string {
if icons == nil {
loadIcons()
}
return icons[name]
}
这个和JAVA中的单例写法基本一致。但是当初写JAVA单例时,会加上关键字 synchronize 。但是加上这个还无法保证单例一定就会只执行一次并且按预想的执行。因为计算机执行指令时满足串行一致的基础上自由地重排访问内存的顺序。解释就是:指令的执行可能在没有上下文依赖的情况下,可以先执行其他的指令。这就可能导致多协程下判断 icons == nil
时 icons 还没全部初始化完成,却走到 icons[name]
,此时无法返回预期结果。于是JAVA的做法就是防止指令重排顺序,JVM利用的是计算机内部的读写栅栏隔离,其实类似加锁。关键字是:[volatile](https://blog.csdn.net/xiaolyuh123/article/details/103289570 "volatile")
。
同理,Go的单例优化后也可以加锁:
var lock sync.Mutex
func Icon(name string) string {
lock.Lock() //加锁
if icons == nil {
loadIcons()
}
lock.Unlock()
return icons[name]
}
但是Go在底层已经实现了只执行一次的方法,如下:
var loadIconsOnce sync.Once
func Icon(name string) string {
loadIconsOnce.Do(loadIcons)
return icons[name]
}
思考:sync.Once 执行一次和自己加锁的有什么区别吗?
欢迎你的回答,等你的回答!!!
最近借着大家的评论又看了下Go的源码,之前没有注意到Go的原子操作,于是去看了下。现在再回来看就很清楚了。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: