Go并发编程-Once

Go并发编程-Once

​ 只执行一次。常用于单例对象的延迟初始化和并发访问下的只需一次初始化的共享资源或者在测试时候初始化测试资源。

简单用

​ sync.Once 只暴露了一个Do方法,多次调用时只会执行一次

func main() {
	o := sync.Once{}
	o.Do(GetConn)
	o.Do(GetConn)
}

func GetConn() {
	fmt.Println("获取连接")
}

​ 分析以上代码:main方法中调用了两次do方法,但是GetConn只执行了一次。

看实现

type Once struct {
	done uint32 //执行的标记
	m    Mutex //互斥锁,有并发goroutine执行时,进入doSlow方法
}

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()
  // 双检查机制,done==0则为第一次执行
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

别踩坑

  1. 死锁

    once的嵌套使用,once中会调用f函数,但如果f函数中又调用了once.do,就会进入到无限递归当中。导致死锁

    func main() {
    	var o = sync.Once{}
    	o.Do(func() {
    		GetConn(&o)
    	})
    }
    
    func GetConn(o *sync.Once) {
    	o.Do(func() {
    		fmt.Println("do something")
    	})
    	fmt.Println("获取连接")
    }
    
  2. 未初始化成功导致panic

    第一次未初始化成功,以后也都不会初始化了。遇到此类问题可以扩展使用once,在调用do方法时再次尝试初始化

    type Conn struct {
    	flag int
    }
    
    func (c *Conn) DoSomething() {
    	fmt.Println("conn's flag", c.flag)
    }
    func main() {
    	var o = sync.Once{}
    	var conn *Conn
    	o.Do(func() {
    		conn, _ = GetConn()
    	})
    	conn.DoSomething()
    }
    
    func GetConn() (*Conn, error) {
    	fmt.Println("获取连接")
    	return nil, errors.New("获取失败")
    }
    
  3. 有时需要reset掉once,go推荐使用一个新的once实例来实现,但是使用新的once的时候可能会犯错

    type MuOnce struct {
    	sync.Once
    	sync.RWMutex
    	mtime time.Time
    	vals  []string
    }
    
    func (m *MuOnce) refresh() {
    	m.Lock()
    	defer m.Unlock()
    
    	m.Once = sync.Once{}
    	m.mtime = time.Now()
    	m.vals = []string{m.mtime.String()}
    }
    
    //超过时间就重新初始化
    func (m *MuOnce) strings() []string {
    	now := time.Now()
    	m.Lock()
    	if now.After(m.mtime) {
    		defer m.Do(m.refresh)
    	}
    	vals := m.vals
    	m.Unlock()
    	return vals
    }
    

    以上代码分析:以上代码会panic,原因是在refersh函数中,更新了sync.Once的指针,势必会导致unlock一个未加锁的mutex

扩展用

  1. Once.Do失败后,调用Do时再次尝试初始化

    type once struct {
    	done uint32
    	sync.Mutex
    }
    
    func (o *once) Do(f func() error) error {
    	if atomic.LoadUint32(&o.done) == 1 {
    		return nil
    	}
    	return o.slowDo(f)
    }
    
    func (o *once) slowDo(f func() error) error {
    	o.Lock()
    	defer o.Unlock()
    
    	var err error
    	if o.done == 0 {
        // 如果有错误就没有赋值,下次再调用Do时还会初始化
    		err = f()
    		if err == nil {
    			atomic.StoreUint32(&o.done, 1)
    		}
    	}
    	return err
    }
    
  2. once并没有返回当前的状态,不知道是否初始化过,可以自己扩展一个用来标记是否初始化的,这样方便查看是否有初始化过

    type Once struct {
    	sync.Once
    }
    
    func (o *Once) Done() bool {
    	return atomic.LoadUint32((*uint32)(unsafe.Pointer(&o.Once))) == 1
    }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值