您好,登錄后才能下訂單哦!
這篇文章主要講解了“go sync.Once如何實現高效單例模式”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“go 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
函數,最終也只會創建一個對象。
在 Go 語言中,全局變量會在程序啟動時自動初始化。因此,如果在定義全局變量時給它賦值,則對象的創建也會在程序啟動時完成,可以通過此來實現單例模式,以下是一個示例代碼:
type MySingleton struct { // 字段定義 } var mySingletonInstance = &MySingleton{ // 初始化字段 } func GetMySingletonInstance() *MySingleton { return mySingletonInstance }
在上面的代碼中,我們定義了一個全局變量 mySingletonInstance
并在定義時進行了賦值,從而在程序啟動時完成了對象的創建和初始化。在 GetMySingletonInstance
函數中,我們可以直接返回全局變量 mySingletonInstance
,從而實現單例模式。
在 Go 語言中,我們可以使用 init
函數來實現單例模式。init
函數是在包被加載時自動執行的函數,因此我們可以在其中創建并初始化單例對象,從而保證在程序啟動時就完成對象的創建。以下是一個示例代碼:
package main type MySingleton struct { // 字段定義 } var mySingletonInstance *MySingleton func init() { mySingletonInstance = &MySingleton{ // 初始化字段 } } func GetMySingletonInstance() *MySingleton { return mySingletonInstance }
在上面的代碼中,我們定義了一個包級別的全局變量 mySingletonInstance
,并在 init
函數中創建并初始化了該對象。在 GetMySingletonInstance
函數中,我們直接返回該全局變量,從而實現單例模式。
在 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 都需要等待,這可能會導致程序變慢。
相對于init
方法和使用全局變量定義賦值單例模式的實現,sync.Once
實現單例模式可以實現延遲初始化,即在第一次使用單例對象時才進行創建和初始化。這可以避免在程序啟動時就進行對象的創建和初始化,以及可能造成的資源的浪費。
而相對于使用互斥鎖實現單例模式,使用 sync.Once
實現單例模式的優點在于更為簡單和高效。sync.Once提供了一個簡單的接口,只需要傳遞一個初始化函數即可。相比互斥鎖實現方式需要手動處理鎖、判斷等操作,使用起來更加方便。而且使用互斥鎖實現單例模式需要在每次訪問單例對象時進行加鎖和解鎖操作,這會增加額外的開銷。而使用 sync.Once
實現單例模式則可以避免這些開銷,只需要在第一次訪問單例對象時進行一次初始化操作即可。
但是也不是說sync.Once
便適合所有的場景,這個是需要具體情況具體分析的。下面說明sync.Once
和init
方法,在哪些場景下使用init
更好,在哪些場景下使用sync.Once
更好。
對于init
實現單例,比較適用于在程序啟動時就需要初始化變量的場景。因為init
函數是在程序運行前執行的,可以確保變量在程序運行時已經被初始化。
對于需要延遲初始化某些對象,對象被創建出來并不會被馬上使用,或者可能用不到,例如創建數據庫連接池等。這時候使用sync.Once
就非常合適。它可以保證對象只被初始化一次,并且在需要使用時才會被創建,避免不必要的資源浪費。
這里首先需要介紹下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) {} // 等等...
接下來需要對這些函數來進行實現。
首先從使用出發,這里使用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等方法都是使用同一個實例操作的效果。
這里想要達到的目的,其實是GET/POST/Run等抽取出來的函數,使用同一個gin.Engine
實例。
為了達到這個目的,我們其實可以在定義internalEngine
變量時,便對其進行賦值;或者是通init
函數完成對internalEngine
變量的賦值,其實都可以。
但是我們抽取出來的函數,用戶并不一定使用,定義時便初始化或者在init
方法中便完成了對變量的賦值,用戶沒使用的話,創建出來的gin.Engine
實例沒有實際用途,造成了不必要的資源的浪費。
而engine方法使用sync.Once
實現了internalEngin
的延遲初始化,只有在真正使用到internalEngine
時,才會對其進行初始化,避免了不必要的資源的浪費。
這里其實也印證了上面我們所說的sync.Once
的適用場景,對于不會馬上使用的單例對象,此時可以使用sync.Once
來實現。
感謝各位的閱讀,以上就是“go sync.Once如何實現高效單例模式”的內容了,經過本文的學習后,相信大家對go sync.Once如何實現高效單例模式這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。