Golang:单例模式

之前写过一篇《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", &amp;s, s)
	fmt.Printf("初始地址:%p,值:%v\n", &amp;s1, s1)
	user := Users{Name: "呆小猴"}
	for i := 0; i &lt; 10; i++ {
		wg.Add(1)
		go func() {
			lock.Lock()
			s = s.Create(&amp;user)
			fmt.Printf("s地址:%p,值:%v\n", &amp;s, s)
			lock.Unlock()
			// s1.Create(&amp;user)
			wg.Done()
		}()
	}
	for i := 0; i &lt; 10; i++ {
		wg.Add(1)
		go func() {
			lock.Lock()
			s1 = s1.Create(&amp;user)
			fmt.Printf("s1地址:%p,值:%v\n", &amp;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&amp;parseTime=True&amp;loc=Local"), &amp;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

数据库初始化

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
	}
}

 

获取单例

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的时候,才会分配空间。

 

除非注明,否则均为呆小猴博客原创文章,转载必须以链接形式标明本文链接!付费资源为虚拟物品,一经出售,概不退款!
呆小猴 » Golang:单例模式

发表回复

呆小猴 · 专注安全学习与分享

关于作者 联系作者