Go实现双检查单例
约 406 字大约 1 分钟
2024-12-08
不安全的双检锁
存在并发问题的代码
type Once struct {
m sync.Mutex
done uint32
}
func (o *Once) Do(f func()) {
if o.done == 1 {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
o.done = 1
f()
}
}
问题原因
在Golang中,对超过机器字(64bit、32bit)大小的值进行读写,可以看作是对拆成 word 大小的几个读写无序进行。
因为Golang中对变量的读和写都没有原子性的保证,所以很可能出现这种情况:锁里边变量赋值只处理了一半,锁外边的另一个goroutine就读到了未完全赋值的变量。所以这个双检锁的实现是不安全的。
Golang中将这种问题称为data race,说的是对某个数据产生了并发读写,读到的数据不可预测,可能产生问题,甚至导致程序崩溃。可以在构建或者运行时使用 -race 参数来检测这种情况。
为什么使用sync.Once可以实现安全单例
源码很短,可以看出,这里用的也是双检锁的模式,只不过将读/写换成了原子读/写,这样可以保证读写不会同时发生,能够读到当前最新的值。
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
}
检测 Data Race的方法
$ go test -race mypkg // to test the package
$ go run -race mysrc.go // to run the source file
$ go build -race mycmd // to build the command
$ go install -race mypkg // to install the package