sync.Once是Golang标准库中的一个同步工具,作用是保证指定函数只被执行一次,可以用于并发安全的单次初始化、单次执行等场景。
sync.Once的实现原理是基于原子性操作和锁的机制,只有一个方法Do(f func()),在第一次调用Do时,会执行函数f并将once对象标记为已完成;第二次及以后调用Do时,将不再执行函数f。看个例子:
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("只执行一次")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
本例中开启了10个goroutine,每个goroutine中都调用了once.Do(onceBody),但onceBody方法只执行了一次。
sync.Once内部使用了一个bool类型的标志位,记录了对应函数是否已经被执行过。当Do方法第一次被调用时,该方法会获取锁并检查标志位,如果标志位为false,则执行函数并将标志位设置为 true,否则直接返回锁并退出。通过原子性的CAS操作进行设置和读取,保证并发的正确性。
假如想要实现一个对象的延迟初始化,只有在第一次被访问时才进行初始化操作,可以使用sync.Once来实现,代码如下:
type MyObject struct {
// 懒加载初始化参数
initParams string
// 初始化后的值
value string
// once对象
once sync.Once
}
// 初始化函数,只被调用一次
func (o *MyObject) init() {
o.value = "initialized with " + o.initParams
}
// 获取对象的value字段,如果对象还没有初始化,则初始化之后再返回
func (o *MyObject) Value() string {
o.once.Do(o.init)
return o.value
}
使用了sync.Once实现了对象的懒加载,保证了并发访问的安全性和初始化只被执行一次。当第一个goroutine调用Value方法时,会执行init函数,初始化MyObject的value字段,并标记MyObject对象的once已经执行过。后续的其他goroutine再调用Value方法时,直接返回value字段,不再进行初始化。