0%

GO SYNC MAP原理

SYNC MAP

map不支持并发读写,原因是:

  • map底层的hmapflags做了状态标志,并发读写会panic
  • 底层控制的本质是防止扩容时,读map操作读到旧桶,写map正在做扩容迁移,将旧桶数据迁移到新桶,从而造成数据读取不正确

解决map并发问题

加锁(mutex)

加锁会导致同一时刻只能一个协程就操作map,性能比较差

sync map

map结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Map struct {
mu Mutex
read atomic.Value // readOnly
dirty map[interface{}]*entry
misses int
}

type readOnly struct {
m map[interface{}]*entry
amended bool
}

type entry struct {
p unsafe.Pointer // *interface{}
}

map struct

正常读写

正常读写操作read map,对map进行读取添加或修改操作

追加

比如追加的d=>D的kv:

先去read map查找有没有d,需要对dirty mapmutex锁,防止其他协程操作dirty map,然后在dirty map追加d,并将d.entry指向万能指针,由万能指针指向对应的值
同时将readmapamended赋值为true
append map

append map success

追加后的读

先去read map去看有没有该k,没有检查amended,如果为true则去查dirty map,并将misses++,当misses加到和dirty map kv数量相等时,提升dirty mapread map

sync map dirty提升流程:
remove read map
dirty map up

当再一次追加新元素时会重建dirty map
rebuild dirty map

追加后再删除
  • 正常删除d:
    正常删除主要操作read map,删除流程参考下图
    delete
  • 追加d后再次删除d:
    先去read map去看有没有该k,没有检查amended,如果为true加锁,去dirty map查找,找到后删除k,并将pointer指向nil
    append delete
    然后将dirty map提升至read map,amended改为false
    append delete dirty up
    最后下次追加时重建dirty map
    append delete rebuild dirty
    重建dirty map时,由于read map此时d指向nil,所以重建dirty map不会重建d
    之后操作read map,并将d标记为expunged,提醒后面操作read map的d时,不用改为nil,直接从map当前buckets删除

由于sync map只有在追加时才会操作dirty map,所以可理解追加、读写分离

总结

  • map在扩容时存在并发问题
  • sync map使用dirty mapread map解决扩容问题
  • 不存在扩容操作时直接读写read map
  • 存在扩容操作时操作dirty map