Golang 的单例模式

本文详细介绍了Golang中单例模式的实现,通过sync.Once结构和doSlow函数确保在并发情况下仅实例化一个对象,避免并发问题。重点讨论了atomic.LoadUint32与atomic.CompareAndSwapUint32的区别及其在单例模式中的正确用法。

介绍

在日常工作中, 单例模式的应用场景很多, 比如加载应用配置, 只需加载一次即可, 即:同一个类只实例化一个对象, golang中的单例模式是通过sync.Once(f func())来实现的。

源码解析

type Once struct {
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

func (o *Once) Do(f func()) {
	// Note: Here is an incorrect implementation of Do:
	//
	//	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
	//		f()
	//	}
	//
	// Do guarantees that when it returns, f has finished.
	// This implementation would not implement that guarantee:
	// given two simultaneous calls, the winner of the cas would
	// call f, and the second would return immediately, without
	// waiting for the first's call to f to complete.
	// This is why the slow path falls back to a mutex, and why
	// the atomic.StoreUint32 must be delayed until after f returns.

	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		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()
	}
}

Once结构体由互斥锁m和无符号整型变量done组成, done标识着f是否执行完成, 完成则置为1。

Do函数中由原子操作 atomic.LoadUint32(&o.done) == 0判断done标志位,  那么这里有一个问题,可否使用atomic.CompareAndSwapUint32(&o.done, 0, 1)来替换atomic.LoadUint32(&o.done) == 0这个操作呢? 答案是否定的。

atomic.CompareAndSwapUint32(&o.done, 0, 1)

CAS的作用是判断done的值是否为0, 若为0则将其置为1, 并且返回true;否则,直接返回false。这里的done状态的变更是前置的, 也就意味着, 在并发的场景中, 就会导致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()
	}
}

在doSlow函数中

if o.done == 0 

这个判断是很有必要的, 因为在并发场景中,  两个协程都执行了atomic.LoadUint32(&o.done) == 0这个判断, 并且结果为true, 如果在doSlow中不做二次校验就会new出两个对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值