golang之map併發訪問
golang中的map不是併發安全的,併發對map讀寫可能會有問題,如:
// N太小時不會(比如10),因機器而異
// fatal error: concurrent map read and map write
func mapDemo1() {
m := make(map[string]int)
go func() {
for i := 0; i < N; i++ {
m[strconv.Itoa(i)] = i // write
}
}()
go func() {
for i := 0; i < N; i++ {
fmt.Println(i, m[strconv. Itoa(i)]) // read
}
}()
time.Sleep(time.Second * 5)
}
上面的程式碼, 開了兩個goroutine,一個對map寫,一個對map讀。
在N較小時(應該要因機器而異,不是太明確,測試時N=10不會panic),在N較大時(比如1000),就會報錯:
fatal error: concurrent map read and map write
…
意思就是map併發讀寫是有問題的,會panic掉。
那麼怎麼辦呢?
在go1.9以前,可以加鎖實現:
type Cmap struct {
m map[string]int
//lock *sync.Mutex
lock *sync.RWMutex
}
func (c *Cmap) Get(key string) int {
c.lock.RLock()
defer c.lock.RUnlock()
return c.m[key]
}
func (c *Cmap) Set(key string, val int) {
c.lock.Lock()
defer c.lock.Unlock()
c.m[key] = val
}
這樣的話,Cmap就是自身帶有鎖的map,在讀和寫的位置加鎖就可以:
func mapDemo2() {
m := make(map[string]int)
//lock := new(sync.Mutex)
lock := new(sync.RWMutex)
cm := Cmap{
m: m,
lock: lock,
}
go func() {
for i := 0; i < N; i++ {
cm.Set(strconv.Itoa(i), i)
}
}()
go func() {
for i := 0; i < N; i++ {
fmt.Println(i, cm.Get(strconv.Itoa(i)))
}
}()
time.Sleep(time.Second * 5)
}
注意上述的兩句註釋,你可以用sync.Mutex (互斥鎖),或者sync.RWMutex(讀寫鎖, 支援多個讀鎖),對上述程式碼實際執行結果不影響。
不過在go版本1.9開始,有了一個sync.map的東西,對併發做了支援。將上面的例子用sync.map
改造如下:
func mapDemo3() {
var m sync.Map
go func() {
for i := 0; i < N; i++ {
m.Store(strconv.Itoa(i), i) // 寫
}
}()
go func() {
for i := 0; i < N; i++ {
v, _ := m.Load(strconv.Itoa(i)) // 讀
fmt.Println(i, v)
}
}()
time.Sleep(time.Second * 5)
}
可以看到Store就是寫,Load就是讀。
還有幾個方法,說明如下:
-
刪除
func (m *Map) Delete(key interface{})
-
遍歷,類似於js的forEach迴圈
func (m *Map) Range(f func(key, value interface{}) bool)
-
存或者取
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
這個方法有點繞,舉例說下:var m sync.Map actual, loaded := m.LoadOrStore("k1", "v1") fmt.Println(actual, loaded) // v1 false actual, loaded = m.LoadOrStore("k1", "v1") fmt.Println(actual, loaded) // v1 true actual, loaded = m.LoadOrStore("k1", "v2") fmt.Println(actual, loaded) // // v1 true actual, loaded = m.LoadOrStore("k2", "v2") fmt.Println(actual, loaded) // v2 false
仔細看完上面的例子,就會理解,這個LoadOrStore方法其實是先取,
如果有key的話就返回,沒有再儲存,其實就是優先取,沒有再儲存
;
和快取的做法很類似:先從快取中取,沒有就從資料庫中讀,讀回來存入快取,下次就可以從快取中取到了。
歡迎補充指正!