golang单例模式最佳实践

本文探讨了Golang中的单例模式,强调了单例模式在资源管理和配置管理上的优势。文章分析了五种不同的实现方式,从线程安全到性能优化,包括非线程安全的实例1、加锁的实例2、检查-锁定的实例3、使用原子操作的实例4以及使用`sync.Once`的实例5,并指出实例5是最佳实践,确保正确性和性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式

单例模式是一种常用的软件设计模式,在使用过程中,单例对象的类只有一个实例。使用单例模式,1 可以节省内存等资源,例如windows操作系统的资源管理器只有一个就够了。2 方便配置管理,例如,服务器的配置信息存放在一个文件中,配置信息由一个单例实例统一读取,进场中的其他对象通过这个单例实例获取配置信息。

单例模式常见写法分析

1. 实例1

下面这段代码有什么问题,在学校的时候,我们可能写出的代码就是下面这个样子,感觉没啥问题,但是在工业实践中是有问题的,从多线程角度思考,就会发现下面的代码是非线程安全的,比如有两个线程都在调用NewInstance1函数,线程A在执行到if instance==nil后 cpu切换到线程B执行,直到线程B运行完,这时instance已经被实例化,当cpu在切回到线程A继续执行的时候,对instance又执行实例化操作,这时内存中已有Instance的两个实例,违背了单例定义。

type Instance struct{}

//非线程安全
func NewInstance1() *Instance {
	if instance == nil {
		instance = &Instance{}
	}
	return instance
}

2.实例2

既然实例1是非线程安全的,自然想到的是对Instance实例化加锁操作,如下面的实例2,这样多个线程在执行NewInstance2的时候是串行的,所以就不会出现实例1中的问题,但是这样的写法是最好的吗,从性能角度考虑是不是最佳的呢?在执行NewInstance2的时候是串行的,效率会受到影响,有没有改进措施呢,见实例3

var lock sync.Mutex

//lock-check模式
func NewInstance2() *Instance {
	lock.Lock()
	defer lock.Unlock()
	if instance == nil {
		instance = &Instance{}
	}
	return instance
}
实例3

实例3采用的是先判断方式,如果instance已经被实例化,直接返回就行了。如果没有实例化,在进行加锁创建实例,即执行实例2的操作。这样就可以并发执行。上面的实例2是先lock在check方式,简称lock-check模式,实例称为check-lock-check模式。继续思考,实例3有没有问题呢?if instance==nil是原子操作吗?

//check-lock-check模式
func NewInstance3() *Instance {
	if instance == nil {
		lock.Lock()
		if instance == nil {
			instance = &Instance{}
		}
		lock.Unlock()
	}
	return instance
}
实例4

既然实例3 比较操作是非原子操作,那将比较这句改成原子操作,golang 提供了原子操作package atomic,申请一个uint32变量做标记,如果是1表示已实例化,如果是0表示么有实例化。实例4已经非常好了,在golang有么有更好的方法呢,

var initlized uint32

//check-lock-check
func NewInstance4() *Instance {
	if atomic.LoadUint32(&initlized) == 1 {
		return instance
	}

	lock.Lock()
	defer lock.Unlock()

	if initlized == 0 {
		instance = &Instance{}
		atomic.StoreUint32(&initlized, 1)
	}
	return instance
}

实例5

在golang中,atomic包中提供一个sync.Once类型,该类型有个Do方法,只会执行一次,
所以将实例化方法放在Do中,只会实例化一次,非常完美的方法,所以实例5是最佳实践方法。

//best
func NewInstance5() *Instance {
	if instance == nil {
		once.Do(func() {
			instance = &Instance{}
		})
	}
	return instance
}

分析总结

  • 在写代码的时候,要保证正确性第一,然后在考虑性能优化的问题,实例1和实例3在正确性上没有保证。
  • 其次才是考虑性能优化,有没有更好的方法,像实例2性能不友好,实例4就比实例2有提升,实例5是最佳实践方法。
  • 当然实例3方法可以放在init函数中调用,进程启动之后再掉用其实也没有问题,因为instance已经被实例化过了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值