簡單介紹
map數(shù)據(jù)類型在很多語言中都有,是一個(gè)key端逼,value形式的hash表朗兵,從而將key,value進(jìn)行一一映射顶滩,進(jìn)行快速查找余掖、添加、刪除等操作礁鲁。在Go語言中也不例外盐欺,提供了map數(shù)據(jù)結(jié)構(gòu)類型。
內(nèi)建map切忌開箱即用
Golang中仅醇,map是引用類型找田,如指針切片一樣,通過下面的代碼聲明后指向的是nil着憨。這點(diǎn)在golang官方文檔中也說明了墩衙,所以千萬別直接聲明后就使用,開始可能經(jīng)常會(huì)犯下面的錯(cuò):
var m map[string]string
m["result"] = "result"
上面的第一行代碼并沒有對map進(jìn)行一個(gè)初始化甲抖,而卻對其進(jìn)行寫入操作漆改,就是對空指針的引用,這將會(huì)造成一個(gè)painc准谚。所以挫剑,得記得用make函數(shù)對其進(jìn)行分配內(nèi)存和初始化:
m := make(map[string]string)
m["result"] = "result"
golang中的map并不是并發(fā)安全的
經(jīng)常使用map,平時(shí)用著也很爽柱衔,但是突然某天流量上來了樊破,程序不知不覺就掛了,還不清楚是怎么回事唆铐,明明以前用著好好的呀哲戚。所以有些好習(xí)慣在剛開始就養(yǎng)成,比如斷言檢查艾岂,并發(fā)安全考慮等顺少。
map縱然很好用,但也得謹(jǐn)慎惯驼≌钍荩或許很多人還不知道奈籽,golang內(nèi)建map其實(shí)并不是并發(fā)安全的伟阔,下面我自定義了一個(gè)結(jié)構(gòu)體,賦其map屬性埂息,給其綁定Get肮塞,Set方法方便操作逾一。請看下面代碼:
// M
type M struct {
Map map[string]string
}
// Set ...
func (m *M) Set(key, value string) {
m.Map[key] = value
}
// Get ...
func (m *M) Get(key string) string {
return m.Map[key]
}
上面的代碼中几蜻,給一個(gè)結(jié)構(gòu)體賦予了一個(gè)map屬性喇潘,且綁定了兩個(gè)方法,進(jìn)行讀寫操作入蛆。當(dāng)你在寫了一個(gè)test在單個(gè)goroutine中跑的時(shí)候或許沒什么問題响蓉,甚至10個(gè)goroutine硕勿,20個(gè)都還正常哨毁。
// TestMap ...
func TestMap(t *testing.T) {
c := helper.M{Map: make(map[string]string)}
wg := sync.WaitGroup{}
for i := 0; i < 21; i++ {
wg.Add(1)
go func(n int) {
k, v := strconv.Itoa(n), strconv.Itoa(n)
c.Set(k, v)
t.Logf("k=:%v,v:=%v\n", k, c.Get(k))
wg.Done()
}(i)
}
wg.Wait()
t.Log("ok finished.")
}
然而當(dāng)你你再添加的時(shí)候,goroutine再增加的時(shí)候源武,會(huì)報(bào)下面的錯(cuò),也就是map并發(fā)寫入出錯(cuò).
fatal error: concurrent map writes
goroutine 75 [running]:
runtime.throw(0x13b2011, 0x15)
需要說明一點(diǎn)的是扼褪,在Http請求時(shí),我們通常對參數(shù)封裝粱栖,encode话浇,可能會(huì)調(diào)用golang自帶的url.Values{},通過讀源碼可以發(fā)現(xiàn)也是線程不安全的闹究。
如何解決
其實(shí)要解決上面的問題也不難幔崖,出錯(cuò)原因golang已經(jīng)寫得很清楚了,concurrent map writes,并發(fā)寫map異常渣淤,這個(gè)時(shí)候肯定想的是并發(fā)操作上能不能解決赏寇。
很顯然,我們可以用鎖機(jī)制解決上面的問題价认。我們將上面的map結(jié)構(gòu)改成如下:
// M
type M struct {
Map map[string]string
lock sync.RWMutex // 加鎖
}
// Set ...
func (m *M) Set(key, value string) {
m.lock.Lock()
defer m.lock.Unlock()
m.Map[key] = value
}
// Get ...
func (m *M) Get(key string) string {
return m.Map[key]
}
在上面的代碼中嗅定,我們引入了鎖機(jī)制操作,從而保證了map在多個(gè)goroutine中的安全用踩。這時(shí)再執(zhí)行我們的test會(huì)發(fā)現(xiàn)其正常輸出渠退。
=== RUN TestMap
--- PASS: TestMap (0.00s)
map_test.go:27: k=:5,v:=5
map_test.go:27: k=:17,v:=17
map_test.go:27: k=:20,v:=20
map_test.go:27: k=:19,v:=19
map_test.go:27: k=:6,v:=6
或許你可以嘗試下sync.Map
golang中的sync.Map是并發(fā)安全的,其實(shí)也就是sync包中g(shù)olang自定義的一個(gè)名叫Map的結(jié)構(gòu)體脐彩。結(jié)構(gòu)體原型如下:
type Map struct {
mu Mutex
read atomic.Value // readOnly
dirty map[interface{}]*entry
misses int
}
可以看見有 Mutex碎乃,很顯然也是用了鎖機(jī)制的,從而來保證了并發(fā)安全惠奸。該包中的Map提供了Store荠锭、Load、Delete晨川、Range等操证九。并且sync包中的Map是開箱可用的删豺,也即是聲明后就可以直接使用,如下:
var m sync.Map
m.Store("method", "eth_getBlockByHash")
value, ok := m.Load("method")
t.Logf("value=%v,ok=%v\n",value,ok)
當(dāng)然也可以用Range遍歷
// TestMap ...
func TestMap(t *testing.T) {
var m sync.Map
m.Store("method", "eth_getBlockByHash")
m.Store("jsonrpc", "2.0")
//value, ok := m.Load("method")
//t.Logf("value=%v,ok=%v\n", value, ok)
//f := func(key, value string) {
//
//}
//f("method", "eth_getBlockByHash")
m.Range(func(key, value interface{}) bool {
t.Logf("range k:%v,v=%v\n", key, value)
return true
})
}
結(jié)果如下:
=== RUN TestMap
--- PASS: TestMap (0.00s)
map_test.go:43: range k:jsonrpc,v=2.0
map_test.go:43: range k:method,v=eth_getBlockByHash
PASS
一個(gè)游蕩于文藝界與IT界的草根人物愧怜。