網站首頁 編程語言 正文
背景
關于 Go 語言的 Map,有兩個需要注意的特性:
- Map 是并發讀寫不安全的,這是出于性能的考慮;
- Map 并發讀寫導致的錯誤,無法使用
recover
捕獲。
后者意味著,只有出現并發讀寫的問題,服務就會掛掉。
這兩個特性可能大家都知道,可即使有這個共識,我還是見過這個問題導致的事故。
事故的大致情況是,一個人封裝了map的讀寫,沒有使用鎖。另一個人開協程讀寫 map。而測試環境請求量小,不一定會導致崩潰,于是,這個問題就留到生產環境才出現了。
除了靠開發者自覺和 code review,還能怎么預防這種情況呢?我覺得在單元測試加入并行測試也很重要。
并行單元測試
單元測試默認不是并發的,比如下面的單測,是可以通過的:
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
方法,它表示當前測試會和其他測試并行運行。 如果參數有-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"] } }) }
這次執行就會報錯:
fatal error: concurrent map read and map write
支持并發的 Map
讓 Map 支持并發讀寫并不麻煩,常見的做法有:
- 操作 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標準庫自帶的,支持并發讀寫的 map:sync.Map
。
sync.Map
sync.Map 就是線程安全版的 map[interface{}]interface{}
,零值可以直接使用,值不能復制。它主要用于以下場景:
- 當同一個 key 的值,寫少讀多的時候;
- 但多個 goroutines 讀寫或修改一系列不同的key的時候。
上面兩種場景中,比起帶Mutex
(或RWMutex
)的map,sync.Map 會大大減少鎖的競爭。
sync.Map 提供的方法不多,這里列出一些。注意的是,any 是 go 1.18 中 interface{}的別名。
Store,設置 key-value。
func (m *Map) Store(key, value any)
Load, 根據 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,不存在時設置)。
我們給上面的單測,使用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
相關推薦
- 2022-11-21 Nginx解決history模式下頁面刷新404問題示例_nginx
- 2024-04-05 mybatis(mybatis-plus)報invalid bound statement (not
- 2023-02-15 Python實現PING命令的示例代碼_python
- 2022-08-17 C語言堆結構處理TopK問題詳解_C 語言
- 2022-06-12 Python中property屬性的用處詳解_python
- 2022-09-13 Oracle使用fy_recover_data恢復truncate刪除的數據_oracle
- 2023-03-18 詳解Flutter中key的正確使用方式_Android
- 2022-10-27 React?State與生命周期詳細介紹_React
- 最近更新
-
- 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同步修改后的遠程分支