使用sync.Once實現高效的單例模式

来源:https://www.cnblogs.com/chenjiazhan/archive/2023/03/16/17223968.html
-Advertisement-
Play Games

一、前期準備 1、首先需要安裝並配置好本地JDK(WIN+R輸入cmd,輸入java -version如下圖) 2、下載maven到本地(鏈接Maven – Download Apache Maven) 其他歷史版本在這裡找:Index of /maven/maven-3 (apache.org) ...


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.Onceinit方法,在哪些場景下使用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可以更方便地實現單例,同時也能夠避免了不必要的資源浪費。當然,沒有任何一種實現是適合所有場景的,我們需要根據具體場景具體分析。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這篇文章主要聊一下緩存,如何使用緩存來加速你的系統,減少磁碟 IO。按照讀寫性質,緩存可以分為讀寫緩存和只讀緩存,兩種緩存有各自的適用場景。 ...
  • 這篇文章主要用來討論Kafka是如何做到高性能的,包括使用批處理方式處理消息,使用順序讀寫的方式使用磁碟,利用PageCache緩存數據並減少IO操作,使用零拷貝技術加速消費流程。 ...
  • 三維模型幾何糾正方法主要包括以下幾種:坐標變換法:通過對三維模型的坐標進行變換,實現幾何糾正。常用的坐標變換包括平移、旋轉和縮放等。平移和旋轉可以通過對模型的平移和旋轉矩陣進行計算實現,縮放可以通過對模型的坐標進行縮放繫數的計算實現。點雲擬合法:將三維模型擬合到點雲數據上,通過對擬合誤差進行優化,實 ...
  • 1. G1垃圾回收器 1.1. 垃圾優先(garbage first) 1.2. 在堆內離散的區域上進行操作 1.2.1. 預設大約有2048個 1.2.2. 代的區域不需要是連續的 1.2.3. 可能屬於老年代 1.2.3.1. 併發後臺線程尋找沒有被引用的對象時,一些區域會比其他區域有更多的垃圾 ...
  • 01_GoLand debug時出現Connected並且程式卡住的問題 環境:win10、go version go1.19.4 windows/amd64、GoLand 2020.3.5 x64 現象 : 在 debug 模式下運行項目,打上斷點後,可以進入斷點位置,也可以跳轉到下個斷點,但是, ...
  • 編寫程式過程中,我們有時不希望改變某個變數的值。此時就可以使用關鍵字 const 對變數的類型加以限定。 初始化和const 因為const對象一旦創建後其值就不能再改變,所以const對象必須初始化。一如既往,初始值可以是任意複雜的表達式: const int i = get_size();//正 ...
  • yaml 1.yaml介紹 YAML是 "YAML Ain't a Markup Language" (YAML不是一種標記語言)的遞歸縮寫。在開發這種語言時,YAML的意思其實是:"Yet Another Markup Language"(仍是一種標記語言),是為了強調這種語言以數據為中心,而不是 ...
  • Lombok、Spring-Initializer 1.Lombok 1.1Lombok介紹 Lombok的作用是: 簡化Javabean的開發,可以使用Lombok的註解讓代碼更加簡潔 Java項目中,很多沒有技術含量又必須存在的代碼:比如POJO類的getter、setter、toString方 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...