您好,登錄后才能下訂單哦!
這篇文章主要講解了“go單例怎么實現雙重檢測是否安全”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“go單例怎么實現雙重檢測是否安全”吧!
當前有的項目直接使用Mutex鎖,有的就直接判斷nil則創建,對于前者,每次都加鎖性能差,對于后者則會出現多個實例,也就不是單例了
進而想要改進一下,在這不討論餓漢和線程非安全的實現,對于go中線程安全的懶漢實現,常見兩種:
雙重檢驗sync.Once
package main import ( "sync" "testing" ) var ( instance *int lock sync.Mutex func getInstance() *int { if instance == nil { lock.Lock() defer lock.Unlock() if instance == nil { i := 1 instance = &i } } return instance } // 用于下邊基準測試 func BenchmarkSprintf(b *testing.B){ for i:=0;i<b.N;i++{ go getInstance()
基于java中雙重檢驗鎖的經驗,因為jvm的內存模型,雙重檢驗鎖會出現可見性問題,可以通過 volatile解決
那么在go里會有類似問題嗎?
關鍵點在于instance變量的讀和寫是否是原子操作
這里做了個race競態檢測:
可以看到20行的寫入和14行的讀取發生了競態
上例中用64位(系統是64位)的int指針表示一個實例,也說明了對于64位數據的寫入和讀取是非原子操作
我們看另一種實現:sync.Once方法
package main import ( "sync" "testing" ) var ( instance *int once sync.Once func getInstance() *int { once.Do(func(){ if instance == nil { i := 1 instance = &i } }) return instance } func BenchmarkSprintf(b *testing.B){ for i:=0;i<b.N;i++{ go getInstance() }
實現比雙重檢驗看起來要整潔許多
race檢測結果:
沒有發生競態
那么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內部其實也是一個雙重檢驗鎖,但是對于共享變量(done字段)的讀和寫使用了atomic包的StoreUint32和LoadUint32方法
sync.Once使用一個32位無符號整數表示共享變量,即使是32位變量的讀寫操作都需要atomic包方法來實現原子性,更說明了go里邊指針的讀寫不能保證原子性
感謝各位的閱讀,以上就是“go單例怎么實現雙重檢測是否安全”的內容了,經過本文的學習后,相信大家對go單例怎么實現雙重檢測是否安全這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。