I. Misdescription

Error code 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")}Copy the code

Execute code output:

Ii. Error cause analysis

Error: FATAL error: sync: unlock of unlocked mutex In the unlocked code, the UNLOCKED mutex is unlocked.

Learn about the internals of the following sync.once package:

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()
	}
}
Copy the code

The sync.once package contains a Mutex variable that performs lock and unlock operations during Do.

We analyze the assignment of the once variable in the source code:

	once.Do(func() {
		DemoTest()
		once = sync.Once{}
	})
Copy the code

Once is an assignment of a value, and the second assignment overrides the first assignment. So, once executes a lock() operation on its internal mutex when entering the Do function. Once is a new non-lock object when performing a secondary assignment, while Do() continues to perform a lock operation on the same address. Defer O.m.lock () before return generates a program error.

Iii. Code modification

The above error is caused by copying the value of the variable of the structure containing the lock. The new variable overwrites the original variable, and the operation is performed at the same address. Therefore, we can assign the variable by pointer, so that the new variable will not overwrite the original variable.

func main() {
	var once *sync.Once
	once = &sync.Once{}
	once.Do(func() {
		DemoTest()
		once = &sync.Once{}
	})
	once.Do(func() {
		DemoTest()
	})
}
Copy the code

Execution Result: