2-4.sync.Once的实现

Go 语言标准库中 sync.Once 可以保证在 Go 程序运行期间的某段代码只会执行一次。

其原理也很简单:用一个数字表示是否执行过 + 锁保证并发场景下只执行一次。后续再次执行时,检查该变量即可。

type Once struct {
    done uint32 // 用来标识代码块是否执行过
    m    Mutex  // 互斥锁
}

func (o *Once) Do(f func()) {
    // done==0,说明没执行过,进入慢路径
    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) // 执行成功后将 done 变量修改为1
        f()
    }
}

在使用该结构体时,我们也需要注意以下的问题:

  • sync.Once.Do 方法中传入的函数只会被执行一次,哪怕函数中发生了 panic

  • 两次调用 sync.Once.Do 方法传入不同的函数只会执行第一次调传入的函数

Last updated