go單例實現(xiàn)雙重檢測是否安全的示例代碼
今天看到項目中的kafka客戶端包裝結(jié)構(gòu)體的獲取是單例模式<br>單例的實現(xiàn)是老生常談的問題了,懶漢餓漢線程安全,因為看到項目中寫的還是有些問題,網(wǎng)上go單例實現(xiàn)的搜索結(jié)果比較少經(jīng)測試也并不靠譜,所以在這記錄下
現(xiàn)狀
當前有的項目直接使用Mutex鎖,有的就直接判斷nil則創(chuàng)建,對于前者,每次都加鎖性能差,對于后者則會出現(xiàn)多個實例,也就不是單例了
改進
進而想要改進一下,在這不討論餓漢和線程非安全的實現(xiàn),對于go中線程安全的懶漢實現(xiàn),常見兩種:
雙重檢驗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中雙重檢驗鎖的經(jīng)驗,因為jvm的內(nèi)存模型,雙重檢驗鎖會出現(xiàn)可見性問題,可以通過 volatile解決
那么在go里會有類似問題嗎?
關(guān)鍵點在于instance變量的讀和寫是否是原子操作
這里做了個race競態(tài)檢測:

可以看到20行的寫入和14行的讀取發(fā)生了競態(tài)
上例中用64位(系統(tǒng)是64位)的int指針表示一個實例,也說明了對于64位數(shù)據(jù)的寫入和讀取是非原子操作
我們看另一種實現(xiàn):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()
}實現(xiàn)比雙重檢驗看起來要整潔許多
race檢測結(jié)果:

沒有發(fā)生競態(tài)
關(guān)于sync.Once
那么sync.Once是怎么實現(xiàn)的呢
看下源碼:
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內(nèi)部其實也是一個雙重檢驗鎖,但是對于共享變量(done字段)的讀和寫使用了atomic包的StoreUint32和LoadUint32方法
sync.Once使用一個32位無符號整數(shù)表示共享變量,即使是32位變量的讀寫操作都需要atomic包方法來實現(xiàn)原子性,更說明了go里邊指針的讀寫不能保證原子性
關(guān)于atomic和metex
引用一段話:https://ms2008.github.io/2019/05/12/golang-data-race/
解決 race 的問題時,無非就是上鎖。可能很多人都聽說過一個高逼格的詞叫「無鎖隊列」。 都一聽到加鎖就覺得很 low,那無鎖又是怎么一回事?其實就是利用 atomic 特性,那 atomic 會比 mutex 有什么好處呢?go race detector 的作者總結(jié)了這兩者的一個區(qū)別:
Mutexes do no scale. Atomic loads do.
mutex 由操作系統(tǒng)實現(xiàn),而 atomic 包中的原子操作則由底層硬件直接提供支持。在 CPU 實現(xiàn)的指令集里,有一些指令被封裝進了 atomic 包,這些指令在執(zhí)行的過程中是不允許中斷(interrupt)的,因此原子操作可以在 lock-free 的情況下保證并發(fā)安全,并且它的性能也能做到隨 CPU 個數(shù)的增多而線性擴展。
若實現(xiàn)相同的功能,后者通常會更有效率,并且更能利用計算機多核的優(yōu)勢。所以,以后當我們想并發(fā)安全的更新一些變量的時候,我們應(yīng)該優(yōu)先選擇用 atomic 來實現(xiàn)。
結(jié)論
- go單例實現(xiàn)—雙重檢測法對共享變量直接讀取和賦值是不安全的,需要atomic包實現(xiàn)原子操作的讀寫
- 對于懶漢模式單例的實現(xiàn),sync.Once是更好的辦法,簡潔安全,sync.Once已經(jīng)幫我們實現(xiàn)了安全的雙重檢驗,能做到加載完成后不再加鎖
- 這里也提醒我們,只要是對于共享變量的并發(fā)訪問,一定要注意安全性,go更推崇避免共享變量,使用chan來交流信息,如果無法避免共享內(nèi)存,優(yōu)先使用atomic實現(xiàn),其次sync,安全第一!
到此這篇關(guān)于go單例實現(xiàn)雙重檢測是否安全的文章就介紹到這了,更多相關(guān)go單例雙重檢測內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GoFrame?gtree樹形結(jié)構(gòu)的使用技巧示例
這篇文章主要為大家介紹了GoFrame?gtree樹形結(jié)構(gòu)的使用技巧示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
Go語言調(diào)用ffmpeg-api實現(xiàn)音頻重采樣
最近對golang處理音視頻很感興趣,對golang音視頻常用庫goav進行了一番研究。自己寫了一個wav轉(zhuǎn)采樣率的功能。給大家分享一下,中間遇到了不少坑,解決的過程中還是蠻有意思的,希望大家能喜歡2022-12-12
go本地環(huán)境配置及vscode go插件安裝的詳細教程
這篇文章主要介紹了go本地環(huán)境配置及vscode go插件安裝的詳細教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05
Golang http包構(gòu)建RESTful API的實現(xiàn)
在Go語言中實現(xiàn)RESTful API可以利用標準庫net/http提供的功能,它允許你輕松地創(chuàng)建和處理HTTP請求,本文主要介紹了Golang http包構(gòu)建RESTful API的實現(xiàn),感興趣的可以了解一下2024-01-01

