MST

星途 面试题库

面试题:Go中sync.Once延迟初始化的基本原理

请阐述Go语言中`sync.Once`是如何实现延迟初始化的?在一个多协程环境下,`sync.Once`怎样保证初始化操作只执行一次,请结合其内部结构和方法进行说明。
32.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. sync.Once内部结构
    • sync.Once结构体定义如下:
    type Once struct {
        done uint32
        m    Mutex
    }
    
    • done字段是一个uint32类型,用于标记初始化是否已经完成。当done为0时,表示尚未初始化;非0时,表示已经初始化。
    • m字段是一个互斥锁,用于在多协程环境下保护对done的读写操作以及初始化函数的执行。
  2. Do方法实现延迟初始化和保证只执行一次
    • sync.Once的核心方法是Do,其定义为:
    func (o *Once) Do(f func()) {
        if atomic.LoadUint32(&o.done) == 0 {
            o.doSlow(f)
        }
    }
    
    • 首先,通过atomic.LoadUint32(&o.done)原子操作读取done的值。如果done已经非0,说明初始化已经完成,直接返回,不再执行初始化函数f
    • 如果done为0,调用doSlow方法:
    func (o *Once) doSlow(f func()) {
        o.m.Lock()
        defer o.m.Unlock()
        if o.done == 0 {
            defer atomic.StoreUint32(&o.done, 1)
            f()
        }
    }
    
    • doSlow方法中,先获取互斥锁o.m,这样可以确保同一时间只有一个协程能够进入初始化逻辑。
    • 再次检查o.done是否为0,这是因为在获取锁之前,可能其他协程已经完成了初始化。
    • 如果o.done仍为0,使用defer atomic.StoreUint32(&o.done, 1)在初始化函数f执行完毕后,将done设置为1,表示初始化完成。然后执行初始化函数f

总结来说,sync.Once通过done字段标记初始化状态,利用原子操作和互斥锁,在多协程环境下保证初始化操作只执行一次,实现延迟初始化。