一.错误描述
造成错误的代码demo
func main() {
var once sync.Once
once.Do(func() {
DemoTest()
once = sync.Once{}
})
once.Do(func() {
DemoTest()
})
}
func DemoTest() {
fmt.Println("This is a test demo for sync.Once")
}
执行代码输出:

二.错误原因分析
查看输出的error:
fatal error: sync: unlock of unlocked mutex
该代码中存在将unlock的mutex进行了unlock的操作。
了解以下以下sync.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包中存在一个Mutex的变量,在执行Do函数的时候执行lock和unlock的操作。
我们分析 源代码中once变量的赋值:
once.Do(func() {
DemoTest()
once = sync.Once{}
})
once是进行的值赋值,第二次赋值会将第一次的赋值进行覆盖。
所以,once在进入Do函数的时候进行了其内部的mutex进行了lock()操作,进行二次值赋值的时候,once是一个新的没有进行lock 操作的,而Do()继续对同一地址上的once进行操作,在return之前进行defer o.m.Unlock() 而产生程序错误。
三.代码修改
上面产生错误的原因是由于对含有锁的结构体的变量进行 值复制导致的,新变量覆盖了原变量,而操作在同一地址上执行导致,所以我们可以通过指针的方式对该变量进行赋值,这样新的变量就不会覆盖原变量。
func main() {
var once *sync.Once
once = &sync.Once{}
once.Do(func() {
DemoTest()
once = &sync.Once{}
})
once.Do(func() {
DemoTest()
})
}
执行结果:
