1. 简介
本文介绍运用sync.Once来完结单例形式,包括单例形式的界说,以及运用sync.Once完结单例形式的示例,同时也比较了其他单例形式的完结。最终以一个开源结构中运用sync.Once完结单例形式的比如来作为结束。
2. 基本完结
2.1 单例形式界说
单例形式是一种创立型规划形式,它保证一个类只有一个实例,并供给一个全局拜访点来拜访这个实例。在整个应用程序中,一切关于这个类的拜访都将返回同一个实例目标。
2.2 sync.Once完结单例形式
下面是一个简略的示例代码,运用 sync.Once
完结单例形式:
package singleton
import "sync"
type singleton struct {
// 单例目标的状况
}
var (
instance *singleton
once sync.Once
)
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
// 初始化单例目标的状况
})
return instance
}
在上面的示例代码中,咱们界说了一个 singleton
结构体表示单例目标的状况,然后将它的实例作为一个包级别的变量 instance
,并运用一个 once
变量来保证 GetInstance
函数只被履行一次。
在 GetInstance
函数中,咱们运用 once.Do
办法来履行一个初始化单例目标。因为 once.Do
办法是根据原子操作完结的,因而能够保证并发安全,即使有多个协程同时调用 GetInstance
函数,最终也只会创立一个目标。
2.3 其他办法完结单例形式
2.3.1 全局变量界说时赋值,完结单例形式
在 Go 语言中,全局变量会在程序发动时主动初始化。因而,假如在界说全局变量时给它赋值,则目标的创立也会在程序发动时完结,能够经过此来完结单例形式,以下是一个示例代码:
type MySingleton struct {
// 字段界说
}
var mySingletonInstance = &MySingleton{
// 初始化字段
}
func GetMySingletonInstance() *MySingleton {
return mySingletonInstance
}
在上面的代码中,咱们界说了一个全局变量 mySingletonInstance
并在界说时进行了赋值,然后在程序发动时完结了目标的创立和初始化。在 GetMySingletonInstance
函数中,咱们能够直接返回全局变量 mySingletonInstance
,然后完结单例形式。
2.3.2 init 函数完结单例形式
在 Go 语言中,咱们能够运用 init
函数来完结单例形式。init
函数是在包被加载时主动履行的函数,因而咱们能够在其间创立并初始化单例目标,然后保证在程序发动时就完结目标的创立。以下是一个示例代码:
package main
type MySingleton struct {
// 字段界说
}
var mySingletonInstance *MySingleton
func init() {
mySingletonInstance = &MySingleton{
// 初始化字段
}
}
func GetMySingletonInstance() *MySingleton {
return mySingletonInstance
}
在上面的代码中,咱们界说了一个包级别的全局变量 mySingletonInstance
,并在 init
函数中创立并初始化了该目标。在 GetMySingletonInstance
函数中,咱们直接返回该全局变量,然后完结单例形式。
2.3.3 运用互斥锁完结单例形式
在 Go 语言中,能够只运用一个互斥锁来完结单例形式。下面是一个简略代码的演示:
var instance *MySingleton
var mu sync.Mutex
func GetMySingletonInstance() *MySingleton {
mu.Lock()
defer mu.Unlock()
if instance == nil {
instance = &MySingleton{
// 初始化字段
}
}
return instance
}
在上面的代码中,咱们运用了一个全局变量instance
来存储单例目标,并运用了一个互斥锁 mu
来保证目标的创立和初始化。详细地,咱们在 GetMySingletonInstance
函数中首先加锁,然后判别 instance
是否现已被创立,假如未被创立,则创立并初始化目标。最终,咱们释放锁并返回单例目标。
需求注意的是,在并发高的情况下,运用一个互斥锁来完结单例形式或许会导致功用问题。因为在一个 goroutine 取得锁并创立目标时,其他的 goroutine 都需求等待,这或许会导致程序变慢。
2.4 运用sync.Once完结单例形式的长处
相关于init
办法和运用全局变量界说赋值单例形式的完结,sync.Once
完结单例形式能够完结推迟初始化,即在第一次运用单例目标时才进行创立和初始化。这能够防止在程序发动时就进行目标的创立和初始化,以及或许形成的资源的糟蹋。
而相关于运用互斥锁完结单例形式,运用 sync.Once
完结单例形式的长处在于更为简略和高效。sync.Once供给了一个简略的接口,只需求传递一个初始化函数即可。比较互斥锁完结办法需求手动处理锁、判别等操作,运用起来更加便利。而且运用互斥锁完结单例形式需求在每次拜访单例目标时进行加锁和解锁操作,这会增加额定的开销。而运用 sync.Once
完结单例形式则能够防止这些开销,只需求在第一次拜访单例目标时进行一次初始化操作即可。
可是也不是说sync.Once
便合适一切的场景,这个是需求详细情况详细分析的。下面说明sync.Once
和init
办法,在哪些场景下运用init
更好,在哪些场景下运用sync.Once
更好。
2.5 sync.Once和init办法适用场景
关于init
完结单例,比较适用于在程序发动时就需求初始化变量的场景。因为init
函数是在程序运行前履行的,能够保证变量在程序运行时现已被初始化。
关于需求推迟初始化某些目标,目标被创立出来并不会被立刻运用,或许或许用不到,例如创立数据库连接池等。这时候运用sync.Once
就非常适宜。它能够保证目标只被初始化一次,并且在需求运用时才会被创立,防止不必要的资源糟蹋。
3. gin中单例形式的运用
3.1 布景
这儿首先需求介绍下gin.Engine
, gin.Engine
是Gin结构的核心组件,负责处理HTTP恳求,路由恳求到对应的处理器,处理器能够是中间件、控制器或处理HTTP响应等。每个gin.Engine
实例都拥有自己的路由表、中间件栈和其他装备项,经过调用其办法能够注册路由、中间件、处理函数等。
一个HTTP服务器,只会存在一个对应的gin.Engine
实例,其保存了路由映射规矩等内容。
为了简化开发者Gin结构的运用,不需求用户创立gin.Engine
实例,便能够完结路由的注册等操作,提高代码的可读性和可维护性,防止重复代码的呈现。这儿关于一些常用的功用,抽取出一些函数来运用,函数签名如下:
// ginS/gins.go
// 加载HTML模版文件
func LoadHTMLGlob(pattern string) {}
// 注册POST恳求处理器
func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {}
// 注册GET恳求处理器
func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {}
// 发动一个HTTP服务器
func Run(addr ...string) (err error) {}
// 等等...
接下来需求对这些函数来进行完结。
3.2 详细完结
首先从运用出发,这儿运用POST办法/GET办法注册恳求处理器,然后运用Run办法发动服务器:
func main() {
// 注册url对应的处理器
POST("/login", func(c *gin.Context) {})
// 注册url对应的处理器
GET("/hello", func(c *gin.Context) {})
// 发动服务
Run(":8080")
}
这儿咱们想要的作用,应该是调用Run办法发动服务后,往/login
途径发送恳求,此刻应该履行咱们注册的对应处理器,往/hello
途径发送恳求也是同理。
所以,这儿POST办法,GET办法,Run办法应该都是对同一个gin.Engine
进行操作的,而不是各自运用各自的gin.Engine
实例,亦或许每次调用就创立一个gin.Engine
实例。这姿态才干到达咱们预想的作用。
所以,咱们需求完结一个办法,获取gin.Engine
实例,每次调用该办法都是获取到同一个实例,这个其实也就是单例的界说。然后POST办法,GET办法又或许是Run办法,调用该办法获取到gin.Engine
实例,然后调用实例去调用对应的办法,完结url处理器的注册或许是服务的发动。这姿态就能够保证是运用同一个gin.Engine
实例了。详细完结如下:
// ginS/gins.go
import (
"github.com/gin-gonic/gin"
)
var once sync.Once
var internalEngine *gin.Engine
func engine() *gin.Engine {
once.Do(func() {
internalEngine = gin.Default()
})
return internalEngine
}
// POST is a shortcut for router.Handle("POST", path, handle)
func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().POST(relativePath, handlers...)
}
// GET is a shortcut for router.Handle("GET", path, handle)
func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().GET(relativePath, handlers...)
}
这儿engine()
办法运用了 sync.Once
完结单例形式,保证每次调用该办法返回的都是同一个 gin.Engine
实例。然后POST/GET/Run办法经过该办法获取到gin.Engine
实例,然后调用实例中对应的办法来完结对应的功用,然后到达POST/GET/Run等办法都是运用同一个实例操作的作用。
3.3 sync.Once完结单例的优点
这儿想要到达的意图,其实是GET/POST/Run等抽取出来的函数,运用同一个gin.Engine
实例。
为了到达这个意图,咱们其实能够在界说internalEngine
变量时,便对其进行赋值;或许是通init
函数完结对internalEngine
变量的赋值,其实都能够。
可是咱们抽取出来的函数,用户并不一定运用,界说时便初始化或许在init
办法中便完结了对变量的赋值,用户没运用的话,创立出来的gin.Engine
实例没有实践用处,形成了不必要的资源的糟蹋。
而engine办法运用sync.Once
完结了internalEngin
的推迟初始化,只有在真正运用到internalEngine
时,才会对其进行初始化,防止了不必要的资源的糟蹋。
这儿其实也印证了上面咱们所说的sync.Once
的适用场景,关于不会立刻运用的单例目标,此刻能够运用sync.Once
来完结。
4.总结
单例形式是一种常用的规划形式,用于保证一个类仅有一个实例。在单例形式中,常常运用互斥锁或许变量赋值的办法来完结单例。然而,运用sync.Once能够更便利地完结单例,同时也能够防止了不必要的资源糟蹋。当然,没有任何一种完结是合适一切场景的,咱们需求根据详细场景详细分析。