介绍
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
*/
本次分享完结,持续享受残余的假期。