sync.Once踩坑经历——sync.Once赋值

1,370 阅读1分钟

一.错误描述

造成错误的代码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()
	})
}

执行结果: