01介绍
关于 golang 语言的 map,已经在「Go 基础」系列文章中介绍过,文末会附上文章链接,建议还没有阅读的读者阅读。我们知道 map 的键必须支持判等操作,本文我们主要讨论的话题是 golang 语言的 map 键类型怎么选择,和 map 是并发安全的吗?
02golang 原生 map 键类型选择
在 golang 语言中,map 可以看作是一个 hash 表,其中 hash 的 key 的类型是受限的,而 val 的类型可以是任意类型。hash 表持有一定数量的 hash 桶, hash 桶均匀存储 hash 表的 key-val 键值对。
在 hash 表中查找某个 key 的 val,我们需要传递 key 给这个 hash 表,在 golang 语言的 map 中,hash 表会先使用 hash 函数把 key 转换为 hash 值,hash 表通过 hash 值的低几位去查找 hash 桶,然后在去查找到的 hash 桶中查找 key,因为 key-val 键值对是成对存储的,所以找到 key 就找到了 val。
现在我们知道,key 是由转换的 hash 值代表的,所以在 golang 语言的 map 中,存储的是 hash 值。
有了上面知识的铺垫,我们回到 map 的键为什么必须支持判等操作的问题上,这是因为我们前面提到的,golang 的 key 是被转换为 hash 值,和 val 成对存储在 hash 桶中,也就是说 golang 需要先定位到一个 hash 桶,然后使用 key 转换的 hash 值与该 hash 桶中存储的 hash 值逐一比对,如果没有相等的,直接返回结果,如果有相等的,就再用 key 本身去比对一次,原因是为了避免 hash 碰撞,只有 hash 值和 key 比对都相等,证明查找到了 key-val 键值对。所以,大家应该明白为什么 golang 语言中 map 的 key 必须支持判等了吧。
funcmapslice
interface{}
func main() {
m1 := map[interface{}]string{
1: "A",
"2": "B",
[]int{}: "C",
}
fmt.Println(m1)
}
Output:
go run main.go
panic: runtime error: hash of unhashable type []int
goroutine 1 [running]:
main.main()
/Users/frank/Desktop/go-concurrent/lesson09/map-key/main.go:8 +0x145
exit status 2
sliceinterface{}
funcmapslice
03构建并发安全的 map
golang 语言的 map 不是并发安全的,即在同一段时间,使用多个 goroutine 操作同一个 map是不安全的。
var m = make(map[int]int)
func main () {
for i := 0; i < 3; i++ {
go store(i, i)
go load(i)
}
time.Sleep(time.Millisecond * 100)
fmt.Println(m)
}
func store(key,val int) {
m[key] = val
}
func load(key int) int {
return m[key]
}
Output:
go run main.go
fatal error: concurrent map writes
goroutine 8 [running]:
runtime.throw(0x10ca6a2, 0x15)
/usr/local/go/src/runtime/panic.go:1117 +0x72 fp=0xc000055760 sp=0xc000055730 pc=0x10327f2
runtime.mapassign_fast64(0x10b1f80, 0xc000100180, 0x1, 0x0)
/usr/local/go/src/runtime/map_fast64.go:101 +0x33e fp=0xc0000557a0 sp=0xc000055760 pc=0x1010c3e
main.store(0x1, 0x1)
/Users/frank/Desktop/go-concurrent/lesson09/map/main.go:24 +0x45 fp=0xc0000557d0 sp=0xc0000557a0 pc=0x10a3525
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc0000557d8 sp=0xc0000557d0 pc=0x10645c1
created by main.main
/Users/frank/Desktop/go-concurrent/lesson09/map/main.go:16 +0x4c
goroutine 1 [sleep]:
time.Sleep(0x5f5e100)
/usr/local/go/src/runtime/time.go:193 +0xd2
main.main()
/Users/frank/Desktop/桌面 - 魏如博的MacBook Pro/go-concurrent/lesson09/map/main.go:19 +0x89
exit status 2
fatal error: concurrent map writes
我们可以使用原生 map,配合 sync 包的 Mutex 和 RWMutex 构建并发安全的 map。
// 构建并发安全的 map
type safeMap struct {
m map[int]int
sync.RWMutex
}
// 构造函数
func newSafeMap() *safeMap {
sm := new(safeMap)
sm.m = make(map[int]int)
return sm
}
// 写
func (s *safeMap) store (key, val int) {
s.Lock()
s.m[key] = val
s.Unlock()
}
// 读
func (s *safeMap) load (key int) int {
s.RLock()
val := s.m[key]
s.RUnlock()
return val
}
func main () {
sm := newSafeMap()
for i := 0; i < 10; i++ {
go sm.store(i, i)
go sm.load(i)
}
time.Sleep(time.Millisecond * 100)
fmt.Println(sm.m)
}
Output:
go run main.go
map[0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9]
阅读上面这段代码,我们通过 RWMutext 和原生 map,实现并发安全的 map。
04golang 并发安全 map
sync.Map
type Map
func (m *Map) Delete(key interface{})
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool)
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
func (m *Map) Range(f func(key, value interface{}) bool)
func (m *Map) Store(key, value interface{})
sync.MapO(1)
sync.Mapinterface{}funcmapslicesync.Map
var m sync.Map
func main () {
for i := 0; i < 10; i++ {
go store(i, i)
go load(i)
}
time.Sleep(time.Millisecond * 100)
m.Range(list)
}
func store (key, val int) {
m.Store(key, val)
}
func load (key int) interface{} {
if val, ok := m.Load(key); ok {
return val
}
return nil
}
func list (key, val interface{}) bool {
fmt.Printf("key:%d=>val:%d\n", key, val)
return true
}
Output:
key:2=>val:2
key:3=>val:3
key:0=>val:0
key:6=>val:6
key:1=>val:1
key:4=>val:4
key:5=>val:5
key:7=>val:7
key:8=>val:8
key:9=>val:9
sync.Map
05总结
sync.Mapsync.Map
转自:Golang语言开发栈