在并發編程中,共享資源的訪問是一個繞不開的問題。Go 中的?
map
?在并發讀寫時是不安全的,直接使用可能導致程序 panic。因此,在多協程同時訪問 Map 的場景下,必須采取有效的同步措施。
本篇將通過一個實戰案例,介紹 Go 的并發神器 ——?sync.Map
,并展示其使用方式和應用場景。
一、為什么普通?map
?不安全?
m?:=?make(map[string]int)go?func()?{m["a"]?=?1
}()go?func()?{fmt.Println(m["a"])
}()
上述代碼在運行時,極有可能 panic:
fatal?error:?concurrent?map?read?and?map?write
這是因為 Go 的原生?map
?類型并不支持并發讀寫。
二、解決方式有哪些?
- 1. 使用?
sync.Mutex
?+ map 手動加鎖; - 2. 使用?
sync.RWMutex
?讀寫分離鎖; - 3. 使用 Go 官方提供的?
sync.Map
?—— 最簡便的線程安全 map 實現!
三、什么是?sync.Map
?
sync.Map
?是 Go 標準庫提供的并發安全 map,特點包括:
- ? 內置鎖機制,適合高并發讀寫;
- ? 無需手動加鎖;
- ? 內部采用讀優化結構(讀多寫少場景性能更佳);
- ? 提供一組特定 API(不能用下標訪問,如?
m["a"]
)。
四、實戰案例:使用?sync.Map
?構建并發計數器
我們實現一個并發訪問的計數器,多個 goroutine 同時對多個 key 進行訪問和計數。
示例代碼:
package?mainimport?("fmt""sync""time"
)func?main()?{var?counter?sync.Mapvar?wg?sync.WaitGroupkeys?:=?[]string{"A",?"B",?"C"}//?每個?key?啟動?10?個協程并發計數for?_,?key?:=?range?keys?{for?i?:=?0;?i?<?10;?i++?{wg.Add(1)go?func(k?string)?{defer?wg.Done()for?j?:=?0;?j?<?100;?j++?{//?原子更新?key?的計數val,?_?:=?counter.LoadOrStore(k,?0)counter.Store(k,?val.(int)+1)}}(key)}}wg.Wait()//?打印結果counter.Range(func(k,?v?any)?bool?{fmt.Printf("Key:?%s,?Count:?%d\n",?k,?v)return?true})
}
五、輸出示例:
Key:?A,?Count:?1000
Key:?B,?Count:?1000
Key:?C,?Count:?1000
六、常用方法介紹:
方法 | 功能描述 |
Load(key) | 獲取鍵對應的值和是否存在 |
Store(key, value) | 設置鍵值 |
LoadOrStore(key, value) | 獲取已存在值,否則存入新值 |
Delete(key) | 刪除指定鍵值對 |
Range(func(k, v any) bool) | 遍歷所有鍵值對,返回 false 可中斷遍歷 |
七、注意事項
- 1.?無法使用?
m["key"]
?形式,只能通過?Load
?/?Store
; - 2.?
LoadOrStore
?非原子加法操作:如本例中?val + 1
?不是并發安全操作;- ? 更好的方式是配合原子操作?
sync/atomic
?或引入鎖;
- ? 更好的方式是配合原子操作?
- 3.?
sync.Map
?更適合“讀多寫少”的場景; - 4. 若是寫多場景,建議仍用?
RWMutex + map
?自行控制。
八、應用場景舉例
- ? 統計類服務:如 PV/UV 實時統計;
- ? 緩存系統:緩存熱點數據;
- ? 注冊中心:維護在線服務列表;
- ? 連接池管理:存儲連接狀態;
九、總結
sync.Map
?提供了一種簡潔、高效的方式來處理并發情況下的共享數據。它避免了手動加鎖的繁瑣,讓我們更專注于業務邏輯。
? 并發安全
? 使用簡單
? 性能優越(適合讀多寫少)
下一個實戰案例:我們將結合?
context + sync.Map
?實現帶過期控制的并發緩存服務,敬請關注!
🔧 附加福利:你可以這樣封裝自己的線程安全結構
type?SafeMap?struct?{data?sync.Map
}func?(sm?*SafeMap)?Incr(key?string)?{for?{val,?_?:=?sm.data.LoadOrStore(key,?0)newVal?:=?val.(int)?+?1sm.data.Store(key,?newVal)break}
}