介绍
sync.Once是一个简略而且弱小的同步原语,应用它能够保障援用的函数只执行一次,常常在初始化配置时候用到该同步原语。
就它的用法看一个示例:
func main() { var once sync.Once for i := 0;i < 10;i++{ go func() { once.Do(func() { fmt.Println("once内") }) }() } time.Sleep(time.Second*5)}/*输入后果:once内*/
能够看到,在并发状况下,该函数只执行了一次。当然,在非并发状况下同样保障只执行一次。
实现
接下来咱们依据这个需要应用channel本人入手实现一个once。
首先定义构造体:
type OnceBak struct { c chan int _//通过接管的channel值来判断是否执行过办法_}
申明一个构造函数:
func NewOnce() *OnceBak { //结构一个有缓冲的通道,避免deadlock谬误 ch := make(chan int,1) //在channel发送一个数字1作为标记位 ch <- 1 return &OnceBak{ c: ch, }}
该构造体实现了一个Do办法,入参是一个函数:
func (o *OnceBak) Do(f func()) { i := <- o.c //判断接管到的是否为1,能够晓得该办法是否执行过 if i == 1{ //执行入参函数 f() //敞开channel的作用 // 1,避免channel读不到数据产生阻塞 //2,从敞开的管道里读到的int类型的默认值0 close(o.c) }}
来做一个小试验测试一下是否合乎预期:
func main() { once := syn.NewOnce() for i := 0;i < 5;i++{ go func() { once.Do(func() { fmt.Println("once中办法.....") }) fmt.Println("once外办法-------") }() } time.Sleep(time.Second)}/*测试后果:once中办法.....once外办法-------once外办法-------once外办法-------once外办法-------once外办法-------*/
能够看到根本满足预期,Do办法里的函数只执行了一次,里面的办法仍旧失常执行。
源码
源码中的sync.once也较为简单,贴出来浏览一下:
type Once struct { done uint32 //标记位 m Mutex //保障原子操作}/*判断一下Once中的done标记位是不是默认值0这里应用的atomic包保障了原子操作*/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只计算Do被调用的次数,而不是调用传入Do的参数的次数,举个例子:
func main() { var once sync.Once f1 := func() {fmt.Println("我是f1")} f2 := func() {fmt.Println("我是f2")} for i := 0;i < 10;i++{ once.Do(f1) once.Do(f2) }}/*该函数的输入后果是:我是f1*/
本次分享完结,持续享受残余的假期。