sync once

109 阅读2分钟

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 总结来说就是通过 原子操作 和 锁 保证只能执行一次,原子操作 存储 执行标识,防止顺序执行数次的可能, 锁保证没有 并发竞争 防止并行执行数次的可能