深入浅出 Go - sync.Map 源码分析
source link: https://xie.infoq.cn/article/ebcb070ee7fd0e273ca53b64f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Go 的 map 在并发场景下,只读是线程安全的,读写则是线程不安全的。Go1.9 提供了并发安全的 sync.Map
,通过阅读源码我们知道 snyc.Map
通过读写分离的机制,降低了锁的粒度,以此来提高并发性能
并发不安全的 map
funcmain(){ m :=make(map[int]int) gofunc(){ for{ m[1] =1 } }() gofunc(){ for{ _ = m[1] } }() time.Sleep(time.Second *10) }
执行程序会报如下错误
$ go run main.go fatal error: concurrent mapreadand map write
并发安全的 sync.Map
对于并发不安全的 map,一般这种情况我们可以通过加锁的方式来实现并发安全的 hashmap,但是锁本身也会带来额外的性能开销,所以 Go1.9 开始标准库提供了并发安全的 sync.Map
,使用起来也很简单,如下
funcmain(){ m := sync.Map{} // 添加元素 m.Store("key","value") // 获取元素 ifvalue, ok := m.Load("key"); ok { fmt.Println(value) } // 遍历 m.Range(func(key, valueinterface{})bool{ fmt.Println(key, value) returntrue }) // 并发读写 gofunc(){ for{ m.Store(1,1) } }() gofunc(){ for{ _, _ = m.Load(1) } }() time.Sleep(time.Second *10) }
sync.Map
直接进入主题,看源码
typeMapstruct{ mu Mutex read atomic.Value// readOnly dirtymap[interface{}]*entry missesint } typereadOnlystruct{ mmap[interface{}]*entry amendedbool }
-
read
只读数据readOnly
-
dirty
读写数据,操作dirty
需要用mu
进行加锁来保证并发安全 -
misses
用于统计有多少次读取read
没有命中 -
amended
用于标记read
和dirty
的数据是否一致
Load
func(m *Map)Load(keyinterface{})(valueinterface{}, okbool){ read, _ := m.read.Load().(readOnly) e, ok := read.m[key] if!ok && read.amended { m.mu.Lock() read, _ = m.read.Load().(readOnly) e, ok = read.m[key] if!ok && read.amended { e, ok = m.dirty[key] m.missLocked() } m.mu.Unlock() } if!ok { returnnil,false } returne.load() }
从 Load
的源码可以看出读取数据时,首先是从 read
读,没有命中的话会到 dirty
读取数据,同时调用 missLocked()
增加 misses
func(m *Map)missLocked(){ m.misses++ ifm.misses <len(m.dirty) { return } m.read.Store(readOnly{m: m.dirty}) m.dirty =nil m.misses =0 }
可以看到当 misses
大于 len(dirty)
时则表示 read
和 dirty
的数据相差太大, sync.Map
会将 dirty
的数据赋值给 read
,而 dirty
会被置空
Store
func(m *Map)Store(key, valueinterface{}){ read, _ := m.read.Load().(readOnly) ife, ok := read.m[key]; ok && e.tryStore(&value) { return } m.mu.Lock() read, _ = m.read.Load().(readOnly) ife, ok := read.m[key]; ok { ife.unexpungeLocked() { m.dirty[key] = e } e.storeLocked(&value) }elseife, ok := m.dirty[key]; ok { e.storeLocked(&value) }else{ if!read.amended { m.dirtyLocked() m.read.Store(readOnly{m: read.m, amended:true}) } m.dirty[key] = newEntry(value) } m.mu.Unlock() }
Store
首先会直接到 read
修改数据,修改成功则直接返回,如果 key
不存在那么就表示要到 dirty
找数据,如果 dirty
存在 key
则修改,如果不存在则新增,同时还要将 read
中的 amended
标记为 true,表示 read
和 dirty
的数据已经不一致了
Range
func(m *Map)Range(ffunc(key, valueinterface{})bool){ read, _ := m.read.Load().(readOnly) ifread.amended { m.mu.Lock() read, _ = m.read.Load().(readOnly) ifread.amended { read = readOnly{m: m.dirty} m.read.Store(read) m.dirty =nil m.misses =0 } m.mu.Unlock() } fork, e :=rangeread.m { v, ok := e.load() if!ok { continue } if!f(k, v) { break } } }
Range
的源码没太多可以说的,有两点需要关注,一个是 Range
会保证 read
和 dirty
是数据同步的,另一个是回调函数返回 false 会导致迭代中断
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK