之前写过一篇《Flutter:Dart单例模式》,但是是面向对象的单例类,像Golang也有面向对象思想,但是没有类这个概念,那么Golang的单例“类”怎么写?
一、饿汉模式
package main
import (
"fmt"
)
func main() {
fmt.Println("实例化")
s := GetInstance()
s1 := GetInstance()
fmt.Printf("地址:%p,值:%v\n", &s, s)
fmt.Printf("地址:%p,值:%v\n", &s1, s1)
fmt.Printf("地址:%p,值:%v\n", &s.name, s.name)
fmt.Printf("地址:%p,值:%v\n", &s1.name, s1.name)
}
var instance *singleTon
// 饿汉
func init() {
fmt.Println("饿汉")
instance = &singleTon{"呆小猴"}
}
type singleTon struct {
name string
}
func GetInstance() *singleTon {
return instance
}
运行结果:
饿汉
实例化
地址:0xc00000a030,值:&{呆小猴}
地址:0xc00000a038,值:&{呆小猴}
地址:0xc000056250,值:呆小猴
地址:0xc000056250,值:呆小猴
二、懒汉模式-sync.Once
package main
import (
"fmt"
"sync"
)
func main() {
fmt.Println("实例化")
s := GetInstance()
s1 := GetInstance()
fmt.Printf("地址:%p,值:%v\n", &s, s)
fmt.Printf("地址:%p,值:%v\n", &s1, s1)
fmt.Printf("地址:%p,值:%v\n", &s.name, s.name)
fmt.Printf("地址:%p,值:%v\n", &s1.name, s1.name)
}
var instance *singleTon
var o sync.Once
type singleTon struct {
name string
}
func GetInstance() *singleTon {
o.Do(func() {
fmt.Println("懒汉")
instance = &SingleTon{"呆小猴"}
})
return instance
}
运行结果:
实例化
懒汉
地址:0xc00000a030,值:&{呆小猴}
地址:0xc00000a038,值:&{呆小猴}
地址:0xc000056250,值:呆小猴
地址:0xc000056250,值:呆小猴
三、实战分析
以Mysql数据库连接为例。
• 不用单例
可以看到mysql连接了两次。不同的*gorm.DB,*config地址是不同的。
package main
import (
"fmt"
"sync"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
fmt.Println("实例化")
s := GetInstance()
s1 := GetInstance()
fmt.Printf("初始地址:%p,值:%v\n", &s, s)
fmt.Printf("初始地址:%p,值:%v\n", &s1, s1)
user := Users{Name: "呆小猴"}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
lock.Lock()
s = s.Create(&user)
fmt.Printf("s地址:%p,值:%v\n", &s, s)
lock.Unlock()
// s1.Create(&user)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
lock.Lock()
s1 = s1.Create(&user)
fmt.Printf("s1地址:%p,值:%v\n", &s1, s1)
lock.Unlock()
wg.Done()
}()
}
wg.Wait()
}
var wg sync.WaitGroup
var lock sync.Mutex
func GetInstance() *gorm.DB {
db := &gorm.DB{}
fmt.Println("懒汉")
db, _ = gorm.Open(mysql.Open("root:123456@/test?charset=utf8&parseTime=True&loc=Local"), &gorm.Config{})
fmt.Println("mysql连接成功")
return db
}
type Users struct {
Name string
}
运行结果:
实例化
懒汉
mysql连接成功
懒汉
mysql连接成功
初始地址:0xc00000aba8,值:&{0xc00011a480 <nil> 0 0xc0001cc380 1}
初始地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 0 0xc000226000 1}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s1地址:0xc00000adb8,值:&{0xc00011a6c0 <nil> 1 0xc0001cc8c0 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a480 <nil> 1 0xc0001cc540 0}
• 不用sync.Once
可以看到mysql连接了一次。不同的*gorm.DB,*config地址是不同的。
import (
"fmt"
"sync"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
fmt.Println("实例化")
s := GetInstance()
s1 := GetInstance()
fmt.Printf("初始地址:%p,值:%v\n", &s, s)
fmt.Printf("初始地址:%p,值:%v\n", &s1, s1)
user := Users{Name: "呆小猴"}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
lock.Lock()
s = s.Create(&user)
fmt.Printf("s地址:%p,值:%v\n", &s, s)
lock.Unlock()
// s1.Create(&user)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
lock.Lock()
s1 = s1.Create(&user)
fmt.Printf("s1地址:%p,值:%v\n", &s1, s1)
lock.Unlock()
wg.Done()
}()
}
wg.Wait()
}
var db *gorm.DB
var wg sync.WaitGroup
var lock sync.Mutex
func GetInstance() *gorm.DB {
fmt.Println("懒汉")
db, _ = gorm.Open(mysql.Open("root:123456@/test?charset=utf8&parseTime=True&loc=Local"), &gorm.Config{})
fmt.Println("mysql连接成功")
return db
}
type Users struct {
Name string
}
运行结果:
实例化
懒汉
mysql连接成功
懒汉
mysql连接成功
初始地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 0 0xc0001cc380 1}
初始地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 0 0xc0000ac000 1}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s1地址:0xc00000adb8,值:&{0xc00011a7e0 <nil> 1 0xc000394540 0}
s地址:0xc00000aba8,值:&{0xc00011a5a0 <nil> 1 0xc0001cc540 0}
• 用sync.Once
可以看到mysql连接了一次。不同的*gorm.DB,*config地址是相同的。
package main
import (
"fmt"
"sync"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
fmt.Println("实例化")
s := GetInstance()
s1 := GetInstance()
fmt.Printf("初始地址:%p,值:%v\n", &s, s)
fmt.Printf("初始地址:%p,值:%v\n", &s1, s1)
user := Users{Name: "呆小猴"}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
lock.Lock()
s = s.Create(&user)
fmt.Printf("s地址:%p,值:%v\n", &s, s)
lock.Unlock()
// s1.Create(&user)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
lock.Lock()
s1 = s1.Create(&user)
fmt.Printf("s1地址:%p,值:%v\n", &s1, s1)
lock.Unlock()
wg.Done()
}()
}
wg.Wait()
}
var db *gorm.DB
var o sync.Once
var wg sync.WaitGroup
var lock sync.Mutex
func GetInstance() *gorm.DB {
o.Do(func() {
fmt.Println("懒汉")
db, _ = gorm.Open(mysql.Open("root:123456@/test?charset=utf8&parseTime=True&loc=Local"), &gorm.Config{})
fmt.Println("mysql连接成功")
})
return db
}
type Users struct {
Name string
}
运行结果:
实例化
懒汉
mysql连接成功
初始地址:0xc00000aba8,值:&{0xc00011a630 <nil> 0 0xc0001cc380 1}
初始地址:0xc00000adb8,值:&{0xc00011a630 <nil> 0 0xc0001cc380 1}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s1地址:0xc00000adb8,值:&{0xc00011a630 <nil> 1 0xc00008e380 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
s地址:0xc00000aba8,值:&{0xc00011a630 <nil> 1 0xc00008e000 0}
四、其他样例
• go-gin-example
Github地址:go-gin-example
数据库初始化
• go-gin-example/models/models.go
// Setup initializes the database instance
func Setup() {
var err error
db, err = gorm.Open(setting.DatabaseSetting.Type, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
setting.DatabaseSetting.User,
setting.DatabaseSetting.Password,
setting.DatabaseSetting.Host,
setting.DatabaseSetting.Name))
... ...
}
获取单例
• go-gin-example/models/models.go
var db *gorm.DB
使用单例
• go-gin-example/models/article.go
func GetArticleTotal(maps interface{}) (int, error) {
var count int
if err := db.Model(&Article{}).Where(maps).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
• gin-vue-admin
Github地址:gin-vue-admin
数据库初始化
• gin-vue-admin/server/initialize/gorm_mysql.go
func GormMysql() *gorm.DB {
m := global.GVA_CONFIG.Mysql
if m.Dbname == "" {
return nil
}
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
return nil
} else {
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
return db
}
}
获取单例
• gin-vue-admin/server/global/global.go
var (
GVA_DB *gorm.DB
... ...
)
• gin-vue-admin/server/main.go
func main() {
... ...
global.GVA_DB = initialize.Gorm() // gorm连接数据库
... ...
}
使用单例
• gin-vue-admin/server/service/system/sys_user.go
func (userService *UserService) Register(u system.SysUser) (userInter system.SysUser, err error) {
var user system.SysUser
if !errors.Is(global.GVA_DB.Where("username = ?", u.Username).First(&user).Error, gorm.ErrRecordNotFound) { // 判断用户名是否注册
return userInter, errors.New("用户名已注册")
}
// 否则 附加uuid 密码hash加密 注册
u.Password = utils.BcryptHash(u.Password)
u.UUID = uuid.NewV4()
err = global.GVA_DB.Create(&u).Error
return u, err
}
注意
• go-gin-example中的db作用域在model包,而gin-vue-admin中的global.GVA_DB作用域在全局。
• GoLang和其他面向对象语言的区别,GoLang单例设置的全局变量,尽管还没有用到,但还是会分配空间,而其他面向对象语言只有在new的时候,才会分配空间。