Go語言單例模式簡述

2020-07-16 10:05:18
設計模式的重要性不用多說,也是面試時常常會被問到的問題。對於設計模式,更多的則是仁者見仁智者見智,要在實際工作中不斷的積累,再進行深度的思考,才能逐漸形成的一種思維。

單例模式也叫單子模式,是常用的模式之一,在它的核心結構中只包含一個被稱為單例的特殊類,能夠保證系統執行中一個類只建立一個範例,本節我們就來介紹一下Go語言中的單例模式。

單例模式實現

Go語言實現單例模式的有四種方式,分別是懶漢式、餓漢式、雙重檢查和 sync.Once。

懶漢式就是建立物件時比較懶,先不急著建立物件,在需要載入組態檔的時候再去建立;餓漢式則是在系統初始化的時候就已經把物件建立好了,需要用的時候直接拿過來用就好了。

不管那種模式最終目的只有一個,就是只範例化一次,只允許一個範例存在。

下面就來分別介紹一下這四種實現方式:

1) 懶漢式——非執行緒安全

非執行緒安全,指的是在多執行緒下可能會建立多次物件。
//使用結構體代替類
type Tool struct {
    values int
}

//建立私有變數
var instance *Tool

//獲取單例物件的方法,參照傳遞返回
func GetInstance() *Tool {
    if instance == nil {
        instance = new(Tool)
    }
    return instance
}
在非執行緒安全的基本上,利用 Sync.Mutex 進行加鎖保證執行緒安全,但由於每次呼叫該方法都進行了加鎖操作,在效能上不是很高效。
//鎖物件
var lock sync.Mutex

//加鎖保證執行緒安全
func GetInstance() *Tool {
    lock.Lock()
    defer lock.Unlock()
    if instance == nil {
        instance = new(Tool)
    }

    return instance
}

2) 餓漢式

直接建立好物件,不需要判斷為空,同時也是執行緒安全,唯一的缺點是在匯入包的同時會建立該物件,並持續占有在記憶體中。

Go語言餓漢式可以使用 init 函數,也可以使用全域性變數。
type cfg struct {
}
var cfg *config
func init()  {
   cfg = new(config)
}
// NewConfig 提供獲取範例的方法
func NewConfig() *config {
   return cfg
}
type config struct {  
}
//全域性變數
var cfg *config = new(config)
// NewConfig 提供獲取範例的方法
func NewConfig() *config {
   return cfg
}

3) 雙重檢查

在懶漢式(執行緒安全)的基礎上再進行優化,減少加鎖的操作,保證執行緒安全的同時不影響效能。
//鎖物件
var lock sync.Mutex

//第一次判斷不加鎖,第二次加鎖保證執行緒安全,一旦物件建立後,獲取物件就不用加鎖了。
func GetInstance() *Tool {
    if instance == nil {
        lock.Lock()
        if instance == nil {
            instance = new(Tool)
        }
        lock.Unlock()
    }
    return instance
}

5) sync.Once

通過 sync.Once 來確保建立物件的方法只執行一次
var once sync.Once

func GetInstance() *Tool {
    once.Do(func() {
        instance = new(Tool)

    })
    return instance
}
sync.Once 內部本質上也是雙重檢查的方式,但在寫法上會比自己寫雙重檢查更簡潔,以下是 Once 的原始碼
func (o *Once) Do(f func()) {
  //判斷是否執行過該方法,如果執行過則不執行
    if atomic.LoadUint32(&o.done) == 1 {
        return
    }
    // Slow-path.
    o.m.Lock()
    defer o.m.Unlock()
  //進行加鎖,再做一次判斷,如果沒有執行,則進行標誌已經掃行並呼叫該方法
    if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}