網站首頁 編程語言 正文
今天看到項目中的kafka客戶端包裝結構體的獲取是單例模式
單例的實現是老生常談的問題了,懶漢餓漢線程安全,因為看到項目中寫的還是有些問題,網上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
是否線程安全
基于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
實現比雙重檢驗看起來要整潔許多
race檢測結果:
沒有發生競態
關于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內部其實也是一個雙重檢驗鎖,但是對于共享變量(done字段)的讀和寫使用了atomic包的StoreUint32和LoadUint32方法
sync.Once使用一個32位無符號整數表示共享變量,即使是32位變量的讀寫操作都需要atomic包方法來實現原子性,更說明了go里邊指針的讀寫不能保證原子性
關于atomic和metex
引用一段話:https://ms2008.github.io/2019/05/12/golang-data-race/
解決 race 的問題時,無非就是上鎖。可能很多人都聽說過一個高逼格的詞叫「無鎖隊列」。 都一聽到加鎖就覺得很 low,那無鎖又是怎么一回事?其實就是利用 atomic 特性,那 atomic 會比 mutex 有什么好處呢?go race detector 的作者總結了這兩者的一個區別:
Mutexes do no scale. Atomic loads do.
mutex 由操作系統實現,而 atomic 包中的原子操作則由底層硬件直接提供支持。在 CPU 實現的指令集里,有一些指令被封裝進了 atomic 包,這些指令在執行的過程中是不允許中斷(interrupt)的,因此原子操作可以在 lock-free 的情況下保證并發安全,并且它的性能也能做到隨 CPU 個數的增多而線性擴展。
若實現相同的功能,后者通常會更有效率,并且更能利用計算機多核的優勢。所以,以后當我們想并發安全的更新一些變量的時候,我們應該優先選擇用 atomic 來實現。
結論
- go單例實現—雙重檢測法對共享變量直接讀取和賦值是不安全的,需要atomic包實現原子操作的讀寫
- 對于懶漢模式單例的實現,sync.Once是更好的辦法,簡潔安全,sync.Once已經幫我們實現了安全的雙重檢驗,能做到加載完成后不再加鎖
- 這里也提醒我們,只要是對于共享變量的并發訪問,一定要注意安全性,go更推崇避免共享變量,使用chan來交流信息,如果無法避免共享內存,優先使用atomic實現,其次sync,安全第一!
原文鏈接:https://blog.csdn.net/q5706503/article/details/105870179
相關推薦
- 2024-03-09 基于 Redis 的 JWT令牌失效方案
- 2022-01-05 npm install 報錯:npm ERR! code EPERM npm ERR! syscal
- 2022-09-24 ASP.NET?MVC把表格導出到Excel_實用技巧
- 2022-01-20 localStorage 和 sessionStorage 及其用法 對象屬性操作方式
- 2023-12-22 onReachBottom觸底觸發事件
- 2022-06-08 記錄一次奇怪的springboot cache redis緩存報錯解決
- 2023-01-03 利用C++模擬實現STL容器:list_C 語言
- 2022-09-07 Python查看Tensor尺寸及查看數據類型的實現_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支