面试题答案
一键面试sync.Once实现原理
- 结构体定义
type Once struct { done uint32 m Mutex }
done
是一个uint32
类型的字段,用于标记初始化是否完成。其值为0表示未初始化,非0表示已初始化。m
是一个互斥锁,用于保证在并发环境下初始化操作的原子性。
- Do方法
func (o *Once) Do(f func()) { 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) f() } }
- 首先,通过
atomic.LoadUint32
原子操作检查done
是否为0。如果不为0,说明初始化已经完成,直接返回,不再执行初始化函数f
。 - 如果
done
为0,调用doSlow
方法。在doSlow
方法中,先获取互斥锁m
,再次检查done
是否为0(这是双检查锁机制,因为在获取锁前可能其他协程已经完成了初始化)。如果仍为0,则执行初始化函数f
,并在f
执行完毕后通过atomic.StoreUint32
原子操作将done
设置为1,表示初始化完成。
- 首先,通过
与单例模式的联系和区别
- 联系
sync.Once
在Go语言中常被用于实现单例模式。单例模式的核心目标是确保一个类仅有一个实例,并提供一个全局访问点。sync.Once
的Do
方法可以保证传入的初始化函数只被执行一次,从而实现类似单例的效果,即在整个程序生命周期内,相关资源只初始化一次。
- 区别
- 设计模式角度:单例模式是一种设计模式,强调的是类的实例唯一性以及全局访问点。而
sync.Once
是Go语言标准库提供的一个工具,专注于保证初始化操作只执行一次,并不直接等同于单例模式的实现。 - 应用场景:单例模式应用场景广泛,可用于各种需要保证实例唯一性的场景,如数据库连接池、日志管理器等。
sync.Once
主要用于Go语言中并发环境下的初始化操作,确保在多协程环境下初始化函数只执行一次。
- 设计模式角度:单例模式是一种设计模式,强调的是类的实例唯一性以及全局访问点。而
使用sync.Once实现单例模式的要点
- 初始化函数:传入
sync.Once.Do
的初始化函数应尽量简单且无副作用。如果初始化函数中存在复杂逻辑或可能失败的操作,需要在函数内部进行相应处理,避免因初始化失败导致单例未正确创建。 - 作用域:确保
sync.Once
实例的作用域与单例实例的期望作用域一致。通常将sync.Once
实例定义为全局变量或包级变量,这样才能保证在整个程序或包内实现单例效果。 - 避免多次初始化:由于
sync.Once
的机制,一旦初始化完成,后续调用Do
方法不会再次执行初始化函数。所以要确保初始化函数能够正确地创建并初始化单例实例,避免出现初始化不完整等情况。