关于golang:手动实现synconce

介绍

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
*/

本次分享完结,持续享受残余的假期。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理