once包保证方法只能执行一次,使用如下
package main
import (
"fmt"
"sync"
)
func main() {
once := sync.Once{}
a := 0
f := func() {
a++
}
once.Do(f)
fmt.Println(a) //1
once.Do(f)
fmt.Println(a) //1
f()
fmt.Println(a) //2没有once包的加持,直接加了
}
结构体如下
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/386),
// and fewer instructions (to calculate offset) on other architectures.
done uint32
m Mutex
}
用done 来表示是否执行过了,至于为什么是uint32,而不是1bit,是因为他需要执行原子操作,使用的方法是下面这个,参数是uint32,所以类型如此
// LoadUint32 atomically loads *addr.
func LoadUint32(addr *uint32) (val uint32)
执行的时候首先看是否已经执行过,用的是原子操作
func (o *Once) Do(f func()) {
// Note: Here is an incorrect implementation of Do:
//
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }
//
// Do guarantees that when it returns, f has finished.
// This implementation would not implement that guarantee:
// given two simultaneous calls, the winner of the cas would
// call f, and the second would return immediately, without
// waiting for the first's call to f to complete.
// This is why the slow path falls back to a mutex, and why
// the atomic.StoreUint32 must be delayed until after f returns.
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
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()
}
}
1.首先要加锁,防止静态资源争夺,其他没有争夺到锁的都会阻塞到锁释放,然后发现已经执行过一次,就结束
2.defer语句中对done加1,可以看出无论f()执行成功与否都会+1,所以其实不保证执行成功与否,只是说执行一次
3.once 总结来说就是通过 原子操作 和 锁 保证只能执行一次,原子操作 存储 执行标识,防止顺序执行数次的可能, 锁保证没有 并发竞争 防止并行执行数次的可能