go-sync.Once-源码分析
目录
go sync.Once 源码分析
sync.Once
是 Go 语言标准库中的一个同步原语,用于确保某个操作或函数在并发环境下只执行一次。它通常用于以下场景:
- 单例模式 :确保全局只有一个实例对象,避免重复创建资源
- 延迟初始化 :在程序运行过程中,当真正需要某个资源时才进行初始化
- 执行性一次的操作 :例如加载配置文件、初始化日志系统等
使用方法
sync.Once
提供了一个方法
Do
,接受一个函数作为参数。无论
Do
被调用多少次,传入的函数只会执行一次。例如:
var once sync.Once
var config *Config
func ReadConfig() *Config {
once.Do(func() {
config = &Config{Server: "example.com", Port: 8080}
})
return config
}
在这个例子中,
config
的初始化只会在第一次调用
ReadConfig
时执行,后续调用直接返回已初始化的
config
。
特点
线程安全
:即使在多个协程同时调用Do
方法,也能保证函数只执行一次。不可重用
:一旦sync.Once
执行完成,它将标记为“已完成”,后续调用不会重新执行。异常处理
:如果Do
中的函数发生 panic,sync.Once
会将其视为“已完成”,后续调用不会重新执行。
源码分析
sync.Once 的实现基于
双重锁检查机制
实现的。它通过
atomic.LoadUint32
和
atomic.StoreUint32
来检查和标记操作是否已完成。这种设计既保证了性能,又确保了线程安全。
package sync
import (
"sync/atomic"
)
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
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() 触发 panic 也会标记为 “已完成”,后续调用不会重新执行
f()
}
}
双重锁检查机制
双重锁检查机制(Double-Checked Locking,DCL)是一种用于多线程环境中实现延迟初始化单例模式的设计模式,其核心思想是通过两次检查来减少同步锁的开销,同时保证线程安全。
工作原理
- 第一次检查 :在进入同步代码块之前,先检查实例是否已经被初始化。如果实例已经存在,则直接返回实例,避免进入同步块。
- 加锁 :如果实例尚未初始化,线程将尝试获取锁,以确保只有一个线程可以进入初始化代码块。
- 第二次检查 :在获取锁后,再次检查实例是否已经被初始化。这是为了确保在当前线程等待锁的过程中,其他线程没有初始化实例。