網(wǎng)站首頁 編程語言 正文
背景
關(guān)于 Go 語言的 Map,有兩個需要注意的特性:
- Map 是并發(fā)讀寫不安全的,這是出于性能的考慮;
- Map 并發(fā)讀寫導(dǎo)致的錯誤,無法使用
recover
捕獲。
后者意味著,只有出現(xiàn)并發(fā)讀寫的問題,服務(wù)就會掛掉。
這兩個特性可能大家都知道,可即使有這個共識,我還是見過這個問題導(dǎo)致的事故。
事故的大致情況是,一個人封裝了map的讀寫,沒有使用鎖。另一個人開協(xié)程讀寫 map。而測試環(huán)境請求量小,不一定會導(dǎo)致崩潰,于是,這個問題就留到生產(chǎn)環(huán)境才出現(xiàn)了。
除了靠開發(fā)者自覺和 code review,還能怎么預(yù)防這種情況呢?我覺得在單元測試加入并行測試也很重要。
并行單元測試
單元測試默認不是并發(fā)的,比如下面的單測,是可以通過的:
func TestConcurrent(t *testing.T) { var m = map[string]int{} // 寫 map t.Run("write", func(t *testing.T) { for i := 0; i < 10000; i++ { m["a"] = 1 } }) // 讀 map t.Run("read", func(t *testing.T) { for i := 0; i < 10000; i++ { _ = m["a"] } }) }
但是我們的期望是,上面的單測不通過,該如何解決呢?
testing.T
有一個 Parallel
方法,它表示當(dāng)前測試會和其他測試并行運行。 如果參數(shù)有-test.count
或-test.cpu
,一個測試可能運行多次,同個測試的多個運行實例,不會并行運行。
我們給上面的單測,加上t.Parallel()
:
func TestConcurrent(t *testing.T) { var m = map[string]int{} t.Run("write", func(t *testing.T) { // 加上并行 t.Parallel() for i := 0; i < 10000; i++ { m["a"] = 1 } }) t.Run("read", func(t *testing.T) { // 加上并行 t.Parallel() for i := 0; i < 10000; i++ { _ = m["a"] } }) }
這次執(zhí)行就會報錯:
fatal error: concurrent map read and map write
支持并發(fā)的 Map
讓 Map 支持并發(fā)讀寫并不麻煩,常見的做法有:
- 操作 map 的時候,加上讀寫鎖
sync.RWMutex
; - 使用 sync.Map。
sync.RWMutex
大家用得可能比較多。這里簡單給個demo。
sync.RWMutex
我們給上面的單測加上鎖,這次運行就能通過了。
func TestConcurrent(t *testing.T) { var m = map[string]int{} // 定義鎖,零值就可以使用 var mu sync.RWMutex t.Run("write", func(t *testing.T) { t.Parallel() for i := 0; i < 10000; i++ { // 鎖 mu.Lock() m["a"] = 1 // 解鎖 mu.Unlock() } }) t.Run("read", func(t *testing.T) { t.Parallel() for i := 0; i < 10000; i++ { // 鎖 mu.Lock() _ = m["a"] // 解鎖 mu.Unlock() } }) }
本文的重點介紹一下Go標準庫自帶的,支持并發(fā)讀寫的 map:sync.Map
。
sync.Map
sync.Map 就是線程安全版的 map[interface{}]interface{}
,零值可以直接使用,值不能復(fù)制。它主要用于以下場景:
- 當(dāng)同一個 key 的值,寫少讀多的時候;
- 但多個 goroutines 讀寫或修改一系列不同的key的時候。
上面兩種場景中,比起帶Mutex
(或RWMutex
)的map,sync.Map 會大大減少鎖的競爭。
sync.Map 提供的方法不多,這里列出一些。注意的是,any 是 go 1.18 中 interface{}的別名。
Store,設(shè)置 key-value。
func (m *Map) Store(key, value any)
Load, 根據(jù) key 讀取 value。
func (m *Map) Load(key any) (value any, ok bool)
Delete,刪除某個key。
func (m *Map) Delete(key any)
Range,遍歷所有key, 如果f
返回false,會停止遍歷。
func (m *Map) Range(f func(key, value any) bool)
還有 LoadAndDelete(讀后刪除)、LoadOrStore(讀key,不存在時設(shè)置)。
我們給上面的單測,使用sync.Map
,測試也可以通過。
func TestConcurrent(t *testing.T) { // 可以使用零值 var m sync.Map t.Run("write", func(t *testing.T) { t.Parallel() for i := 0; i < 10000; i++ { // 寫 m.Store("a", 1) } }) t.Run("read", func(t *testing.T) { t.Parallel() for i := 0; i < 10000; i++ { // 讀 v, ok := m.Load("a") if ok { _ = v.(int) } } }) }
參考
pkg.go.dev/sync#Map
原文鏈接:https://juejin.cn/post/7174771881911222327
相關(guān)推薦
- 2022-06-07 FreeRTOS實時操作系統(tǒng)臨界段保護場合示例_操作系統(tǒng)
- 2022-09-26 Mybatis的一級緩存和二級緩存及其區(qū)別
- 2023-04-19 yarn : 無法加載文件 D:xx\yarn.ps1,因為在此系統(tǒng)上禁止運行腳本。有關(guān)詳細信息,請
- 2022-08-28 SpringCloudAlibaba-3.分布式事務(wù)(Seata)
- 2022-04-22 mac安裝oh-my-zsh出現(xiàn)command not found: npm問題解決
- 2022-11-13 ASP.NET?MVC使用Session會話保持表單狀態(tài)_實用技巧
- 2023-01-30 python多進程程序打包成exe的問題_python
- 2022-07-14 Jquery+Ajax實現(xiàn)跨域訪問_jquery
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支