Go语言设计模式之单例模式
2023-12-16
单例模式的概念非常好理解,但凡接触过设计模式的同学,都能讲出一二来。
1.模式介绍
单例设计模式(Singleton Design Pattern):一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。
单例模式最常见的使用就是在程序启动时候创建加载基础组件:配置文件、实例日志组件、数据库组件、缓存组件等等(很多时候这种加载方式会用IOC容器来实现)。
这些全局唯一的组件实例在程序使用能够带来:提高系统效率(数据库连接池、线程池)、杜绝多线程资源访问冲突(日志处理)等好处。
2.模式demo
根据单例模式实现的方式,可分为:饿汉模式
和懒汉模式
。
饿汉模式
:单例模式最常见实现的方式,在类加载的时候静态实例就已经加载好了。懒汉模式
:只在使用的时候进行加载,有延迟加载的效果。
2.1 饿汉模式实现
由于饿汉模式
是在程序加载的时候就创建并实例化了,所以无需考虑多并发的情况。
1 | package main |
2.2 懒汉模式实现
由于懒汉模式
是在使用的时候再创建实例,属于懒加载;此时程序已经启动并正在运行,此时创建实例可能会出现多线程的情况,所以要考虑并发问题。
2.2.1 普通实现方式
1 | package main |
2.2.2 Go语言特定实现方式
以上都是一般语言通用的实现方式,还有一种Go语言独有实现的方式——使用sync.Once
1 | package main |
是不是很神奇?为什么不用判断instance
是否存在?为什么不用加锁?
这一切在源码面前都没有秘密:
1 | package sync |
实际上就是Go语言在基础库层面帮我们封装了通用的单例模式实现方式。
3.源码解析
3.1 beego的orm链接实例
在beego框架中,orm组件就使用了单例模式。
beego会将数据库的配置信息通过orm.RegisterDataBase("default", "mysql", "root:123456@tcp(127.0.0.1:3306)/beego?charset=utf8")
函数存储至beego的orm结构体orm.alias
中
1 | type alias struct { |
期间还生成数据库连接所使用的Go基础库实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38```Go
type DB struct {
// Atomic access only. At top of struct to prevent mis-alignment
// on 32-bit platforms. Of type time.Duration.
waitDuration int64 // Total time waited for new connections.
connector driver.Connector
// numClosed is an atomic counter which represents a total number of
// closed connections. Stmt.openStmt checks it before cleaning closed
// connections in Stmt.css.
numClosed uint64
mu sync.Mutex // protects following fields
freeConn []*driverConn // free connections ordered by returnedAt oldest to newest
connRequests map[uint64]chan connRequest
nextRequest uint64 // Next key to use in connRequests.
numOpen int // number of opened and pending open connections
// Used to signal the need for new connections
// a goroutine running connectionOpener() reads on this chan and
// maybeOpenNewConnections sends on the chan (one send per needed connection)
// It is closed during db.Close(). The close tells the connectionOpener
// goroutine to exit.
openerCh chan struct{}
closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
maxIdleCount int // zero means defaultMaxIdleConns; negative means 0
maxOpen int // <= 0 means unlimited
maxLifetime time.Duration // maximum amount of time a connection may be reused
maxIdleTime time.Duration // maximum amount of time a connection may be idle before being closed
cleanerCh chan struct{}
waitCount int64 // Total number of connections waited for.
maxIdleClosed int64 // Total number of connections closed due to idle count.
maxIdleTimeClosed int64 // Total number of connections closed due to idle time.
maxLifetimeClosed int64 // Total number of connections closed due to max connection lifetime limit.
stop func() // stop cancels the connection opener.
}
并将sql.DB
实例以属性的方式与orm.alias
关联起来。
实例化如下的结构体:
以上结构体在每次添加(使用addAliasWthDB
函数)的时候会根据“别名alias
”创建唯一的实例。
具体的调用函数为:
1 | func addAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) { |
以上函数中使用一张hash表判断别名alias
为xxx的实例存不存在,这个hash表就在orm包中内部结构体_dbCache
的cache
属性中
1 | // database alias cacher. |
beego的orm注册的流程图如下:
可见,beego中相同alias
的orm实例都是相同的,每次获取实例都是同一个实例,虽然代码书写上与传统的单例模式有些差别,但是本质上还是使用了单例这种设计模式。
请关注我
微信公众号:搜索 码农RyuGou
或者扫码