Golang 單例模式與sync.Once
Golang 單例模式與sync.Once
背景
單例模式可以說是最簡單的設計模式之一了,功能很簡單:一個型別的東西只例項化一次,全域性只有一個例項,並提供方法來獲取該例項。
在 Golang 中變數或說明例項只初始化一次的效果通過init
函式是可以實現的,包在被引入時就會執行一次init
函式且無論同一包被引入多少次也都只執行一次。
不過本文主要想討論的單例模式是第一次需要用到時才去初始化,也就是延遲初始化。
不太好的單例實現
// bad_singleton.go
package main
import (
"sync"
)
var svcMu sync.Mutex
var svc *Svc
type Svc struct {
Num int
}
func GetSvc() *Svc {
if svc == nil { // 這一步判斷不是併發安全的
svcMu.Lock()
defer svcMu.Unlock()
if svc == nil {
svc = &Svc{Num: 1}
svc = &Svc{}
svc.Num = 1
}
}
return svc
}
注意執行互斥鎖svcMu.Lock()
前的語句if svc == nil
並不是併發安全的,即在多個 goroutine 併發呼叫的場景下,其中的一個 goroutine 正在初始化這個變數svc
的過程中,這裡別的 goroutine 判斷得到svc
不等於nil
的結果時也並不意味著svc
就一定完成初始化了。
因為在缺乏顯式同步的情況下,編譯器和CPU在能保證每個 goroutine 內滿足序列一致性的基礎上可以自由地重排訪問記憶體的指令順序。
比如svc = &Svc{Num: 1}
這行看上去只是一條執行語句,可能重排後的一種實現是像下面這樣的:
svc = &Svc{}
svc.Num = 1
可見,不等於nil
並不意味著就一定完成了初始化,因此上面示例是一種不太好的單例實現。
比較好的單例實現
// good_singleton.go
package main
import (
"sync"
)
var svcOnce sync.Once
var svc *Svc
type Svc struct {
Num int
}
func GetSvc() *Svc {
svcOnce.Do(func() {
svc = &Svc{Num: 1}
})
return svc
}
sync.Once
提供的Do
方法無論被呼叫多少次都只執行傳入的函式一次,那為什麼說直接使用Do
方法執行初始化而不是套一層if svc == nil
才是比較好的做法呢,下面結合sync.Once
原始碼來說明。
// sync.Once 原始碼
package sync
import (
"sync/atomic"
)
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 { // 這步是判斷是否已經完成初始化的關鍵
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
官方對於sync.Once
的實現是非常短小精悍的。其中atomic.LoadUint32(&o.done) == 0
是關鍵的一步,這裡採用的是原子操作語句,保證了即使在併發場景下也是安全的,對資料的讀寫都是完整的。
當o.done
的值為0
時表示未進行初始化或正在初始化中,只有等於1
時才表示初始化已經完成,即f()
執行完成後由defer atomic.StoreUint32(&o.done, 1)
語句給o.done
賦值1
;也就是o.done
作為是否完成初始化的標識,可能的值只有前面說的兩個,為0
時則加鎖並嘗試初始化流程,反之則視為已完成初始化直接跳過,這樣就完美兼顧了效率與併發安全。
由此可見sync.Once
內建的初始化完成標識判斷遠比if svc == nil
靠譜,因此像上面這樣使用sync.Once
實現單例模式是最推薦的方式。
額外推薦
實則開發中用到的設計模式經常不止一種,越是複雜大型的專案就越需要使用更多合適的模式來優化程式碼。
下面要推薦的是RefactoringGuru。這是我所見過最好的設計模式教程,是國外建立的一個教程網站,有中文站點,圖文並茂地介紹每一種模式的結構、關係和邏輯,
最重要的是示例程式碼方面囊括了常見的幾種主流程式語言,是個適合多數程式設計師學習設計模式的好地方!
下圖是設計模式的目錄頁面(是不是很圖文並茂呢):
結語
以上為本人學習和實踐的一些總結,如有錯漏還請不吝賜教。
參考
《Go程式設計語言》9.5 延遲初始化:sync.Once 網路版
Go 單例模式講解和程式碼示例
本文來源: Golang 單例模式與sync.Once
- OceanBase榮獲OSCAR兩項大獎,開源已成主流開發模式
- linux根據inode編號刪除檔案
- 特約專訪 | 思否 CEO 高陽帶你瞭解 Code For Better _ Hackathon 冠軍團隊背後的故事
- Nest.js快速啟動API專案
- TiDB Hackathon 2022丨總獎金池超 35 萬!邀你喚醒程式碼世界的更多可能性!
- Go 為什麼能火?歸功於這 5 個方面
- JS 逆向百例】猿人學系列 web 比賽第五題:js 混淆 - 亂碼增強,詳細剖析
- 汪源:資料分析熱詞迭出,“三個統一”值得關注
- Go 為什麼能火?歸功於這 5 個方面
- Go 為什麼能火?歸功於這 5 個方面
- Go 為什麼能火?歸功於這 5 個方面
- Go 為什麼能火?歸功於這 5 個方面
- SpringBoot Vue Flowable,模擬一個請假審批流程!
- Go 為什麼能火?歸功於這 5 個方面
- 聊聊如何利用管道模式來進行業務編排(下篇)
- Golang 單例模式與sync.Once
- Golang 單例模式與sync.Once
- 如何通俗地理解「分散式系統」;Vue是否可以在一個專案中使用多個UI框架;大廠上線流程:先上前端還是後端|極客觀點
- 第二屆 1024 中國工程師文化日議程全覽,你不能錯過的 N 個理由
- 手寫程式語言-遞迴函式是如何實現的?