介绍
sync.Once是一个简单而且强大的同步原语,使用它可以保证引用的函数只执行一次,经常在初始化配置时候用到该同步原语。
就它的用法看一个示例:
func main() {
var once sync.Once
for i := 0;i < 10;i++{
go func() {
once.Do(func() {
fmt.Println("once内")
})
}()
}
time.Sleep(time.Second*5)
}
/*
输出结果:
once内
*/
可以看到,在并发情况下,该函数只执行了一次。当然,在非并发情况下同样保证只执行一次。
实现
接下来我们根据这个需求使用channel自己动手实现一个once。
首先定义结构体:
type OnceBak struct {
c chan int _//通过接收的channel值来判断是否执行过方法_}
声明一个构造函数:
func NewOnce() *OnceBak {
//构造一个有缓冲的通道,防止deadlock错误
ch := make(chan int,1)
//在channel发送一个数字1作为标志位
ch <- 1
return &OnceBak{
c: ch,
}
}
该结构体实现了一个Do方法,入参是一个函数:
func (o *OnceBak) Do(f func()) {
i := <- o.c
//判断接收到的是否为1,可以知道该方法是否执行过
if i == 1{
//执行入参函数
f()
//关闭channel的作用
// 1,防止channel读不到数据发生阻塞
//2,从关闭的管道里读到的int类型的默认值0
close(o.c)
}
}
来做一个小实验测试一下是否符合预期:
func main() {
once := syn.NewOnce()
for i := 0;i < 5;i++{
go func() {
once.Do(func() {
fmt.Println("once中方法.....")
})
fmt.Println("once外方法-------")
}()
}
time.Sleep(time.Second)
}
/*
测试结果:
once中方法.....
once外方法-------
once外方法-------
once外方法-------
once外方法-------
once外方法-------
*/
可以看到基本满足预期,Do方法里的函数只执行了一次,外面的方法依旧正常执行。
源码
源码中的sync.once也较为简单,贴出来阅读一下:
type Once struct {
done uint32 //标志位
m Mutex //保证原子操作
}
/*
判断一下Once中的done标志位是不是默认值0
这里使用的atomic包保证了原子操作
*/
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()
}
}
注意
注意,使用该原语要注意的是,该原语只保证sync.Once只计算Do被调用的次数,而不是调用传入Do的参数的次数,举个例子:
func main() {
var once sync.Once
f1 := func() {fmt.Println("我是f1")}
f2 := func() {fmt.Println("我是f2")}
for i := 0;i < 10;i++{
once.Do(f1)
once.Do(f2)
}
}
/*
该函数的输出结果是:
我是f1
*/
本次分享结束,继续享受剩余的假期。
有疑问加站长微信联系(非本文作者)