「Golang」sync-Once 用法以及源碼講解
前言
在我們開發過程中經常會使用到單例模式這一經典的設計模式,單例模式可以幫助開發者針對某個(些)變量或者對象或者函數(方法)進行在程序運行期間只有一次的初始化或者函數調用操作,比如在開發項目中針對某一類連接池的初始化(如數據庫連接池等)。針對這種情況,我們就需要使用單例模式進行操作。
單例模式🌰
自己搞得單例模式
1type SingletonPattern struct {
2 done bool
3}
4
5func (receiver *SingletonPattern) Do(f func()) {
6 if !receiver.done {
7 f()
8 receiver.done=true
9 }
10}
11
12
那麼如何解決上面的併發的問題呢。此時就可以使用 go 標準庫中所提供的併發原語 ---sync.Once
標準庫真香系列之sync.Once
1type Once struct {
2 // 標記符號,用於標記是否執行過
3 done uint32
4 // 互斥鎖,用於保護併發調用以及防止copy
5 m Mutex
6}
7
8
1func (o *Once) Do(f func()) {
2 // 原子獲取當前 done 字段是否等於0
3 // 如果當前字段等於1
4 // 則代表已經 執行過
5 // 這是第一層校驗
6 if atomic.LoadUint32(&o.done) == 0 {
7 // 如果爲0則代表沒被調用過則調用
8 // 此處寫成一個函數的原因是爲了
9 // 進行函數內聯提升性能
10 o.doSlow(f)
11 }
12}
13
14func (o *Once) doSlow(f func()) {
15 // 此處加鎖用於防止其他goroutine同時訪問調用
16 o.m.Lock()
17 defer o.m.Unlock()
18 // 二次校驗
19 // 爲的是防止多個goroutine進入此函數的時候,可能發生的重複執行 f()
20 if o.done == 0 {
21 // 函數執行結束設置done 字段爲 1代表已經執行完畢
22 defer atomic.StoreUint32(&o.done, 1)
23 // 執行
24 f()
25 }
26}
27
28
sync.Once
的使用問題
哪來的 deadlock?
fatal error: all goroutines are asleep - deadlock!
不要在執行函數中,嵌套當前的
sync.Once
對象 不要在執行函數中,嵌套當前的sync.Once
對象 不要在執行函數中,嵌套當前的sync.Once
對象。(重要的話要說三遍)
哪來的 invalid memory address or nil pointer dereference?
1func main() {
2 var once sync.Once
3 var conn net.Conn
4 once.Do(
5 func() {
6 var err error
7 conn, err = net.Dial("tcp", "")
8 if err != nil {
9 return
10 }
11 })
12 conn.RemoteAddr()
13}
14
15
panic: runtime error: invalid memory address or nil pointer dereference
爲什麼?因爲sync.Once
只保證執行一次,但是不保證執行是否出錯,即我只管調用,出錯了跟我無關,上述代碼中
1conn, err = net.Dial("tcp", "")
2
3
必定出現 err!=nil 的情況,此時如果不對conn
變量進行判斷爲nil
,就會出現空指針異常,那麼,如何來保證他執行成功了呢,我們需要對其進行改造
1type Once struct {
2 once sync.Once
3}
4
5func (receiver *Once) OnceDo(f func() error) error {
6 var err error
7 receiver.once.Do(
8 func() {
9 err = f()
10 })
11 return err
12}
13
14func main() {
15 var once Once
16 var conn net.Conn
17 err := once.OnceDo(
18 func() error {
19 var err error
20 conn, err = net.Dial("tcp", "")
21 if err != nil {
22 return err
23 }
24 return nil
25 })
26 if err != nil {
27 log.Fatal(err)
28 }
29}
30
31
經過封裝,我們就可以得到sync.Once
執行時是否出錯,以適配各種錯誤處理。
此封裝可能會有更好的解決方案,上面的方案也僅僅是一個🌰罷了。
總結
至此sync.Once
的用法以及源碼解析就完成了,可能有些地方有些理解上的錯誤,請各位諒解並且幫忙指出修改意見,如果這篇文章能幫到你,這是我的榮幸。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/f1CttKRRpPSX4fkEDWNw9g