美文网首页
golang sync.Once源代码阅读--让你的初始化只执行

golang sync.Once源代码阅读--让你的初始化只执行

作者: guonaihong | 来源:发表于2020-02-28 21:48 被阅读0次

    sync.Once的用处

    sync.Once 主要用途执行只需要执行一次的初始化函数,比如验证器的初始,http.Client初始化,都可以派上大用场。

    来个直观的例子

    下面的例子比较贴近实战代码,你提供了一个包给别人使用,这个包里面有初始化函数。他对你提供的初始化函数和方法二次封装。只暴露一个函数出来,这时候就可以使用sync.Once包下初始化函数。

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type Client struct {
        url string
    }
    
    func New() *Client {
        return &Client{}
    }
    
    func (c *Client) Look(id int) {
        fmt.Printf("url is %d\n", id)
    }
    
    var (
        defaultClient *Client
        once          sync.Once
    )
    
    func look(id int) {
        once.Do(func() {
            defaultClient = New()
            fmt.Printf("init ok\n")
        })
    
        defaultClient.Look(id)
    }
    func main() {
    
        for i := 0; i < 5; i++ {
            look(i)
        }
    
    }
    

    sync.Once数据结构

    done uint32标记位。如果用户的函数初始化过,标记位置为1。
    m Mutext 锁的作用,是锁住回调函数的执行过程,第一个函数在资源没准备好时,第二个函数过来就要乖乖地等待。

    // Once is an object that will perform exactly one action.
    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/x86),
        // and fewer instructions (to calculate offset) on other architectures.
        done uint32
        m    Mutex
    }
    
    

    实现

    • if atomic.LoadUint32(&o.done) == 0 ,是if o.Done == 0的线程安全写法,如果 o.done等于0才会进下来的逻辑,初始化过自然就不会进的。 atomic.LoadUint32是原子变量取值函数,其实atomic下面包是汇编指令的抽象实现,不同平台会生成该平台对应机器指令,。
    • defer atomic.StoreUint32(&o.done, 1)o.done = 1线程安全写法。当然取值的时候也是要用atomic.LoadUint32取值才有效果的,如果写的时候用atomic.StoreUint32,取值直接用if o.done == 1等于没用。
    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()
        }
    }
    
    

    注意(错误的实现)

    这段是注释里面特别说明的,很多cas狂魔闭眼就能写出如下代码。这段代码其实有问题,
    回调函数还没有初始化成功,这时第二个调用过来立马返回继续执行,资源没准备好就往下跑,panic少不了。

     
        if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
              f()
         }
        
    

    相关文章

      网友评论

          本文标题:golang sync.Once源代码阅读--让你的初始化只执行

          本文链接:https://www.haomeiwen.com/subject/lfurhhtx.html