1.单例模式版本 f1
单例模式就是:所有的协程共用一个实例化对象
type DBClient struct {
Host string
Port int
}
var dbClient *DBClient
func f1() *DBClient {
//如果说,连接对象是空的,那么就需要实例化一下
if dbClient == nil {
dbClient = &DBClient{}
}
return dbClient
}
-
缺点:如果是多个并发的协程,那么就会出现同一个对象被实例化多遍 -
eg : 协程1拿到【&DBClient】后检查发现为nil,所以就进行实例化操作【dbClient = &DBClient{}】,但是就是在这个过程刚做完,还没有进行
return语句和退出之前 -
接着由于高并发的特性,现在需要执行协程2,拿到【&DBClient】后检查发现为nil,所以就进行实例化操作【dbClient = &DBClient{}】,接着又是
return语句和退出之前 -
同理,就这样被实例化无数次,最后哪一个协程先返回 dbClient ,dbClient 才不为空
-
针对这个问题,所以就是要让
实例化操作 + return语句处于一个原子操作中 -
所以:加锁
2.单例模式版本 f2
var lock sync.Mutex
type DBClient struct {
Host string
Port int
}
var dbClient *DBClient
func f2() *DBClient {
lock.Lock()
defer lock.Unlock() //defer执行时机: return语句 defer解锁 退出
if dbClient == nil {
dbClient = &DBClient{}
}
return dbClient
}
缺点:损失了高并发的性能- 这种操作仅仅适用于少量的并发操作
- 一进来就锁住代码,这样尽早的让代码逻辑串行化,是十分损耗性能的
- 所以考虑把上锁拿到判空操作里面
3.单例模式版本 f3
var lock sync.Mutex
type DBClient struct {
Host string
Port int
}
var dbClient *DBClient
func f3() *DBClient {
if dbClient == nil {
//多个协程并发进行判空操作,比提前锁住串行化的性能损失小一些
lock.Lock()
defer lock.Unlock() //defer执行时机: return语句 defer解锁 退出
dbClient = &DBClient{}
}
return dbClient
}
- 貌似没有问题了,但这样写是存在bug的
- 锁只是锁住了实例化的过程,但是dbClient的判空是没有锁的
假设:协程1上锁,然后进行实例化,重点来了,细分实例化的步骤,就会出现a.拿到DBClient结构体,b.取地址写入dbClient变量,c.给结构体的字段挨个赋值上零值- 可以试想一下,在c步骤中,假如赋值了一半字段后,这时由于高并发的特性协程2开始执行,而每一个协程又都是可以抵达dbClient的判空操作的,接着,协程2就一定会得出dbClient不为空,从而就把这个只赋值了一半零值的返回了
- 所以,到这里就需要解决一下这个
允许其他协程抵达判空操作的问题 - 常见的方式,就是让其他协程判断另外一个值来得知dbClient为不为空
4.单例模式版本 f4
var lock sync.Mutex
type DBClient struct {
Host string
Port int
}
var dbClient *DBClient
var initialed bool = false
func f4() *DBClient {
if !initialed {
//多个协程虽然会并发进行判空操作,但是比提前锁住逻辑而串行化的性能损失小一些
lock.Lock()
defer lock.Unlock()
dbClient = &DBClient{}
initialed = true
}
return dbClient
}
缺点:这样是躲过赋值一半零值就返回的问题- 但是新变量【initialed = true】过程,加上判断initialed是不是false,不还是会有这个问题吗
所以:换一个值判断还不够,还需要进行原子性赋值的保证才行
5.单例模式版本 f5
var lock sync.Mutex
type DBClient struct {
Host string
Port int
}
var dbClient *DBClient
var NewInitialed int32 = 0
// 由于原子性赋值操作里面是没有atomic.Loadbool的,所以只能使用int32代替
func f5() *DBClient {
if atomic.LoadInt32(&NewInitialed) == 0 {
lock.Lock()
defer lock.Unlock()
dbClient = &DBClient{}
atomic.StoreInt32(&NewInitialed, 1)
}
if atomic.LoadInt32(&NewInitialed) == 1 {
return dbClient
}
return dbClient
}
截至目前是满足了要求但其实golang自带了一个
sync.Once的包,底层就是单例模式代码
6.单例模式版本--最终版
package main
import (
"sync"
)
type DBClient struct {
Host string
Port int
}
var dbClient *DBClient
var once sync.Once
func f6() *DBClient {
once.Do(func() {
dbClient = &DBClient{}
})
return dbClient
}