Go語言如何實(shí)現(xiàn)線程安全的Map
在并發(fā)編程中,數(shù)據(jù)共享和訪問是一個(gè)重要的主題。Go語言內(nèi)置的map雖然高效,但并不是線程安全的。若在多線程環(huán)境中直接操作map,可能會(huì)引發(fā)并發(fā)寫入的錯(cuò)誤(fatal error: concurrent map writes)。因此,在需要并發(fā)訪問map時(shí),必須采取措施確保線程安全。
本文將介紹如何使用Go語言的泛型和sync.RWMutex實(shí)現(xiàn)一個(gè)線程安全的Map,同時(shí)支持常見的操作,例如增刪改查、遍歷和轉(zhuǎn)化為普通的Map。
1. 為什么需要線程安全的Map
Go語言內(nèi)置的map在多線程環(huán)境中并不安全。例如,以下代碼可能引發(fā)崩潰:
package main
import (
"fmt"
"sync"
)
func main() {
m := make(map[int]int)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
m[i] = i
}(i)
}
wg.Wait()
fmt.Println(m)
}
運(yùn)行上述代碼可能會(huì)報(bào)錯(cuò):fatal error: concurrent map writes。這是因?yàn)閙ap的寫操作沒有加鎖,在多線程中引發(fā)了競態(tài)條件。
2. 如何實(shí)現(xiàn)線程安全的Map
Go標(biāo)準(zhǔn)庫提供了sync.Map,它是線程安全的。但它的API相對(duì)簡單,缺乏泛型支持且性能在某些場(chǎng)景下并不理想。因此,我們可以基于sync.RWMutex和泛型封裝一個(gè)自定義的線程安全Map。
2.1 基本實(shí)現(xiàn)
以下是線程安全SyncMap的完整實(shí)現(xiàn):
package syncmap
import (
"sync"
)
// SyncMap 定義了一個(gè)線程安全的泛型Map
type SyncMap[K comparable, V any] struct {
mu sync.RWMutex
m map[K]V
}
// NewSyncMap 創(chuàng)建一個(gè)新的線程安全的SyncMap
func NewSyncMap[K comparable, V any]() *SyncMap[K, V] {
return &SyncMap[K, V]{
m: make(map[K]V),
}
}
// Load 獲取指定key的值,如果存在返回值和true,否則返回零值和false
func (s *SyncMap[K, V]) Load(key K) (V, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
val, ok := s.m[key]
return val, ok
}
// Store 設(shè)置指定key的值,如果key已存在會(huì)覆蓋舊值
func (s *SyncMap[K, V]) Store(key K, value V) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = value
}
// Has returns true if the key exists in the map.
func (s *SyncMap[K, V]) Has(key K) bool {
s.mu.RLock()
defer s.mu.RUnlock()
_, ok := s.m[key]
return ok
}
// Delete 刪除指定key的值
func (s *SyncMap[K, V]) Delete(key K) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.m, key)
}
// Range 遍歷所有的鍵值對(duì),callback函數(shù)返回false時(shí)停止遍歷
func (s *SyncMap[K, V]) Range(callback func(key K, value V) bool) {
s.mu.RLock()
defer s.mu.RUnlock()
for k, v := range s.m {
if !callback(k, v) {
break
}
}
}
// Len returns the length of the map.
func (s *SyncMap[K, V]) Len() int {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.m)
}
// ToMap 轉(zhuǎn)化為普通的map,返回一個(gè)線程安全的副本
func (s *SyncMap[K, V]) ToMap() map[K]V {
s.mu.RLock()
defer s.mu.RUnlock()
copyMap := make(map[K]V, len(s.m))
for k, v := range s.m {
copyMap[k] = v
}
return copyMap
}
2.2 關(guān)鍵功能說明
線程安全:
- 讀操作使用sync.RWMutex的RLock,允許并發(fā)讀取。
- 寫操作使用sync.RWMutex的Lock,確保寫操作互斥。
支持泛型:
通過K和V泛型參數(shù)支持任意鍵值類型,其中K必須是可比較的。
基本操作:
- Load:獲取值。
- Store:設(shè)置值。
- Has:判斷鍵是否存在。
- Delete:刪除鍵值對(duì)。
- Range:遍歷所有鍵值對(duì)。
- Len:獲取map的長度。
- ToMap:轉(zhuǎn)化為普通map。
3. 使用示例
以下代碼演示了SyncMap的基本用法:
package main
import (
"fmt"
"syncmap"
)
func main() {
// 創(chuàng)建一個(gè)線程安全的Map
m := syncmap.NewSyncMap[string, int]()
// 添加值
m.Store("one", 1)
m.Store("two", 2)
// 獲取值
if val, ok := m.Load("one"); ok {
fmt.Println("Key 'one':", val)
} else {
fmt.Println("Key 'one' not found")
}
// 刪除值
m.Delete("one")
// 遍歷所有鍵值對(duì)
m.Range(func(key string, value int) bool {
fmt.Printf("Key: %s, Value: %d
", key, value)
return true
})
// 轉(zhuǎn)化為普通map
ordinaryMap := m.ToMap()
fmt.Println("Ordinary map:", ordinaryMap)
}
運(yùn)行結(jié)果:
Key 'one': 1
Key: two, Value: 2
Ordinary map: map[two:2]
4. 總結(jié)
自定義線程安全的SyncMap具備以下優(yōu)點(diǎn):
- 泛型支持:靈活適配不同類型的鍵值。
- 線程安全:支持高并發(fā)場(chǎng)景的安全訪問。
- 可擴(kuò)展性:易于添加更多功能,如合并操作、條件更新等。
通過本文的實(shí)現(xiàn)與示例,希望您能更好地理解和應(yīng)用線程安全Map,構(gòu)建健壯的并發(fā)應(yīng)用。
到此這篇關(guān)于Go語言如何實(shí)現(xiàn)線程安全的Map的文章就介紹到這了,更多相關(guān)Go線程安全Map內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- golang中有序Map的實(shí)現(xiàn)
- Go 中的Map與字符處理指南
- Go數(shù)據(jù)結(jié)構(gòu)之映射map方式
- Go語言sync.Map實(shí)現(xiàn)高并發(fā)場(chǎng)景下的安全映射
- golang讀寫分離sync.Map的使用
- Golang HashMap實(shí)現(xiàn)原理解析
- golang遍歷map的方法小結(jié)
- Go中map數(shù)據(jù)類型的實(shí)現(xiàn)
- Go語言中的map擴(kuò)容機(jī)制
- 關(guān)于Golang的Map的線程安全問題的解決方案
- go開發(fā)過程中mapstructure使用示例詳解
- Go 語言中映射(Map)使用場(chǎng)景
相關(guān)文章
Golang Map value不可尋址使用指針類型代替示例詳解
這篇文章主要為大家介紹了Golang Map value不可尋址使用指針類型代替示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解
這篇文章主要為大家介紹了go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Go語言中的定時(shí)器原理與實(shí)戰(zhàn)應(yīng)用
在Go語言中,Timer和Ticker是處理定時(shí)任務(wù)的重要工具,Timer用于一次性事件,而Ticker則用于周期性事件,本文詳細(xì)介紹了這兩種定時(shí)器的創(chuàng)建、使用和停止方法,并通過實(shí)際案例展示了它們?cè)诒O(jiān)控日志、檢查系統(tǒng)狀態(tài)等方面的應(yīng)用2024-10-10
使用go語言解析xml的實(shí)現(xiàn)方法(必看篇)
下面小編就為大家?guī)硪黄褂胓o語言解析xml的實(shí)現(xiàn)方法(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06
Golang中自定義json序列化時(shí)間格式的示例代碼
Go語言作為一個(gè)由Google開發(fā),號(hào)稱互聯(lián)網(wǎng)的C語言的語言,自然也對(duì)JSON格式支持很好,下面這篇文章主要介紹了關(guān)于Golang中自定義json序列化時(shí)間格式的相關(guān)內(nèi)容,下面話不多說了,來一起看看詳細(xì)的介紹吧2024-08-08
go語言實(shí)現(xiàn)聊天服務(wù)器的示例代碼
這篇文章主要介紹了go語言實(shí)現(xiàn)聊天服務(wù)器的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
如何讓shell終端和goland控制臺(tái)輸出彩色的文字
這篇文章主要介紹了如何讓shell終端和goland控制臺(tái)輸出彩色的文字的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-05-05

