[Go并发] - Once源码解析

437 阅读1分钟

Once用于只执行一次的场景,常用在懒汉型单例模式。

var client *Client
var clientOnce sync.Once

func NewClient() *Client{
    clientOnce.Do(func(){
        client = &Client{}
    })
    return client
}

Once源码解析

Oncedone标志和m互斥锁组成。其中,done=0表示Once还没有被执行,done=1表示Once已经被执行。 m 互斥锁用于控制协程对f()方法的互斥访问。

type Once struct {
    done uint32
    m    Mutex
}

Once采用Double-Check双检查机制实现。首先判断“Once是否被执行过”,没有执行过则进入doSlow()函数执行f方法。由于在Once初始化的时候,存在多个协程进入doSlow函数的情况;对此,采用Mutex互斥锁保证只有一个协程执行f方法,再次判断“Once是否被执行过”防止后面的协程再次执行f方法。

func (o *Once) Do(f func()) {
    // 判断Once是否被执行过: 
    if atomic.LoadUint32(&o.done) == 0 {
    // done==0: 没有被执行过,执行doSlow函数
        o.doSlow(f)
    }
    // done==1: 已经执行过,直接退出函数
}


func (o *Once) doSlow(f func()) {
    // 当多个协程执行Once.Do()时,会有多个协程进入这里;
    // 因此,需要通过Mutex互斥锁控制协程对f()函数的访问,保证同一时刻只有一个协程进入
    o.m.Lock()
    defer o.m.Unlock()

    // 进入临界区后,判断Once是否被执行过:
    if o.done == 0 {
        // done==0表示f函数没有被执行过
        // 先执行f(),然后通过原子操作更新done标志位
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
    // done!=0表示f函数已经被执行
}