Go语言高并发,单例,保姆级教程!
在开发中,某些工具类我们只希望他在程序中被创建一次,每次使用时都不会再创建实例。
这个需求我们就可以使用到单例模式来处理。
一、单例模式能解决什么问题?
保证一个类只有一个实例
其实 go 语言中,没有类这个概念,被抽象成了结构体。
但是为了方便大家理解,我们依旧还是用类来描述,望大家能理解,别在这个问题上纠结。
他的运作方式是什么样的呢?
如果你创建了一个对象,过了一会儿再想创建新对象时,此时你获取到的是你之前创建的对象,而不是新的对象。
主要用处:
我们常用他来提供全局工具,通知中心,全局配置等地方。
二、实现方式
我们大致分为以下三步:
-
1、创建一个私有变量用于保存单例类型。 go 语言里面只要是小写字幕开头都属于是私有变量,包外就不能被调用。 -
2、我们需要对外提供一个构建单例的获取方法。 我们一般会以 Instance 来结尾或者开头。 -
3、在公有方法里面去判定私有变量是否被初始化,并返回。
package main
import "fmt"
type AbsentStruct struct {
Name string
}
// 第一步
var Absent *AbsentStruct
// 第二步
func AbsentInstance() *AbsentStruct {
// 第三步
// 如果为nil,代表未初始化,初始化他
if Absent==nil {
Absent = new(AbsentStruct)
}
return Absent
}
func main() {
m1 := AbsentInstance()
m1.Name = "张三"
m2 := AbsentInstance()
m2.Name = "李四"
fmt.Printf("%p,%p \n %s,%s \n",m1,m2,m1.Name,m2.Name)
}
以下是运行结果:
$ go run instance/main.go
0xc000010230,0xc000010230
李四,李四
三、并发情况下的问题
如果在生产中我们使用上面的方式去创建单例,在第三步时,高并发的情况会出现重复创建。
我们改下我们的 main 方法:
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 1000; i++ {
go func() {
m1 := AbsentInstance()
fmt.Printf("%p \n %s \n",m1,m1.Name)
}()
}
wg.Wait()
}
此时再运行:
$ go run instance/main.go
0xc000100000
0xc000100000
0xc000092000
0xc000092000
0xc000092000
我这里只截取了前面几个,你看前面两个和第三个就出现了不一样,后面的数据就基本不会出现异常了,因为私有变量已经被赋值了。
所以我们一般还会在第三步时给他加上锁,保证同一时间只执行一次。
// 第二步
func AbsentInstance() *AbsentStruct {
// 第三步
// 如果为nil,代表未初始化,初始化他
if Absent==nil {
//上锁
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
Absent = new(AbsentStruct)
}
}
return Absent
}
这样就能保证我们在创建实例时,只会有一个线程在跑。
三、更便捷的方法
这样去写单例,代码其实有点多,我们可以使用官方的 sync 包里面的
var once = sync.Once{}
func AbsentInstance() *AbsentStruct {
once.Do(func() {
absent = new(AbsentStruct)
})
return absent
}
是不是一下就清爽了!
你学废了么?