goroutine 與 map 并發(fā)的采坑事件
1. goroutine 與map 的并發(fā)讀寫操作
在Go 1.6之前, 內(nèi)置的map類型是部分goroutine安全的哭当,并發(fā)的讀沒有問題,并發(fā)的寫可能有問題趣惠。自go 1.6之后久妆, 并發(fā)地讀寫map會報錯,這在一些知名的開源庫中都存在這個問題铸磅,所以go 1.9之前的解決方案是額外綁定一個鎖,封裝成一個新的struct或者單獨使用鎖都可以杭朱。
因為map為引用類型阅仔,所以即使函數(shù)傳值調(diào)用,參數(shù)副本依然指向映射m, 所以多個goroutine并發(fā)寫同一個映射m弧械, 寫過多線程程序的同學都知道八酒,對于共享變量,資源刃唐,并發(fā)讀寫會產(chǎn)生競爭的羞迷, 故共享資源遭到破壞
1. 有并發(fā)問題的map
官方的Why are map operations not defined to be atomic? ]已經(jīng)提到內(nèi)建的map不是線程(goroutine)安全的界轩。
... and in those cases where it did, the map was probably part of some larger data structure or computation that was already synchronized.
我們來看一下代碼吧:
一個goroutine一直讀,一個goroutine一只寫同一個鍵值衔瓮,即即使讀寫的鍵不相同浊猾,而且map也沒有"擴容"等操作,代碼還是會報錯热鞍。
func main() {
m := make(map[int]int)
go func() {
for {
_ = m[1]
}
}()
go func() {
for {
m[2] = 2
}
}()
select {}
}
然后你會發(fā)現(xiàn) 運行不起:
fatal error: concurrent map read and map write
有時候數(shù)據(jù)競爭不是很容易發(fā)現(xiàn)葫慎,你可以輸入:
go run --race main.go
進行查看。
2. Go 1.9之前的解決方案
你可以用互斥鎖 sync.Mutex 也可以用讀寫鎖 sync.RWMutex(性能好些)薇宠。
var counter = struct{
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}
讀數(shù)據(jù)的時候很方便的加鎖:
counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)
寫數(shù)據(jù)的時候:
counter.Lock()
counter.m["some_key"]++
counter.Unlock()
當然你也可以單獨使用 讀寫鎖進行讀寫加鎖操作偷办,只是如果有多個的情況下就沒有嵌入結構體那么方便操作了。
3. 終極解決方案 sync.Map
可以說澄港,上面的解決方案相當簡潔椒涯,并且利用讀寫鎖而不是Mutex可以進一步減少讀寫的時候因為鎖帶來的性能。
- Store
- LoadOrStore
- Load
- Delete
- Range
Store(key, value interface{})
存 key,value 存儲一個設置的鍵值回梧。
LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
返回鍵的現(xiàn)有值(如果存在)废岂,否則存儲并返回給定的值,如果是讀取則返回true漂辐,如果是存儲返回false泪喊。
Load(key interface{}) (value interface{}, ok bool)
讀取存儲在map中的值,如果沒有值髓涯,則返回nil。OK的結果表示是否在map中找到值哈扮。
Delete(key interface{})
刪除key,及其value
Range(f func(key, value interface{}) bool)
循環(huán)讀取map中的值.遍歷所有的key,value
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
//Store
m.Store(1,"a")
m.Store(2,"b")
//LoadOrStore
//若key不存在纬纪,則存入key和value,返回false和輸入的value
v,ok := m.LoadOrStore("1","aaa")
fmt.Println(ok,v) //false aaa
//若key已存在滑肉,則返回true和key對應的value包各,不會修改原來的value
v,ok = m.LoadOrStore(1,"aaa")
fmt.Println(ok,v) //false aaa
//Load
v,ok = m.Load(1)
if ok{
fmt.Println("it's an existing key,value is ",v)
} else {
fmt.Println("it's an unknown key")
}
//Range
//遍歷sync.Map, 要求輸入一個func作為參數(shù)
f := func(k, v interface{}) bool {
//這個函數(shù)的入?yún)ⅰ⒊鰠⒌念愋投家呀?jīng)固定靶庙,不能修改
//可以在函數(shù)體內(nèi)編寫自己的代碼问畅,調(diào)用map中的k,v
fmt.Println(k,v)
return true
}
m.Range(f)
//Delete
m.Delete(1)
fmt.Println(m.Load(1))
}
關于 其源碼分析 可參考此文章