Golang源码分析atomic.Once

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010129985/article/details/83276768
package sync

import (
	"sync/atomic"
)
type Once struct {
	m    Mutex
	done uint32
}
func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 1 {
		return
	}
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}
源码很简洁,Once结构体只有两个元素:一个锁定临界区的Mutex互斥锁,一个uint32类型的done标志位,Once是开箱即用的(Mutex是开箱即用的,done的零值就是uint32(0))
Do方法解析:
1 在方法开始进行原子检查done标志位是否为1,如果满足立即返回
2 在条件1不满足情况下,对临界区上锁(写锁)然后检查done的值是否为0
3 如果条件2满足,用defer表达式声明了一个语句:原子的将done的值设置为1;然后执行f函数
4 Do方法内部为什么要做两次done检查:第一个原子检查是为了快速判定done的状态来决定是否尝试获取该Once的互斥锁,毕竟获取互斥锁是可能阻塞的,有可能出现多个goroutine争抢互斥锁,减小争抢锁的概率和发生死锁的概率;进入临界区的检查意义是:虽然能进入临界区的goroutine显然是直到done的值为0的,但这并不能保证接下来的操作就是安全的,因为在if判定done之后到m上锁之间是由间歇的,这里索然没有代码间隙,但是在CPU指令排布上是可以被插入别的指令的(比如在if条件获取了done的值并解锁done之后,另一个goroutine刚好执行完f部分也将done状态改为了1,释放了m锁,那么当前goroutine拿到锁之后如果不判断done的值就不能保证其中的f函数在Do的结构里面只会执行一次)
5 及时f函数执行过程中出现panic,defer表达式也会保证done的值原子的变为1,也就是说有可能出现某些情况下调用了Once.Do方法但实际上初始化并没有成功。
6 Once结构内部元素都是包级私有的,我们无法从外部检测到done的状态
Once.Do方法的效果是:
1 这个方法在一个程序中只会执行一次,重复调用无效(只要是同一个Once)
2 Once.Do(f)方法保护的是这个调用表达式本身只会有效执行一次。而不是保证f只会被执行一次,在其他代码区域还是可以重复调用f函数
3 只有在f函数执行完毕后done的值才会真正变成1(已完成),标志位done变为1后才会解锁m(注意两个defer表达式执行的顺序),这就意味着如果f是一个阻塞的函数,那么本次调用Do方法的goroutine将会一直阻塞,并且Once的状态会保持为0直到f执行结束,如果这个时候还有别的Goroutine也在执行该Once的Do方法那么也将阻塞(等待获取写锁)



猜你喜欢

转载自blog.csdn.net/u010129985/article/details/83276768
今日推荐