深入浅出 Go - sync.Map 源码分析

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 的 map 在并发场景下,只读是线程安全的,读写则是线程不安全的。Go1.9 提供了并发安全的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sync.Map","attrs":{}}],"attrs":{}},{"type":"text","text":",通过阅读源码我们知道 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"snyc.Map","attrs":{}}],"attrs":{}},{"type":"text","text":" 通过读写分离的机制,降低了锁的粒度,以此来提高并发性能","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"并发不安全的 map","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func main() {\n m := make(map[int]int)\n\n go func() {\n for {\n m[1] = 1\n }\n }()\n\n go func() {\n for {\n _ = m[1]\n }\n }()\n\n time.Sleep(time.Second * 10)\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"执行程序会报如下错误","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"$ go run main.go\n\nfatal error: concurrent map read and map write","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"并发安全的 sync.Map","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于并发不安全的 map,一般这种情况我们可以通过加锁的方式来实现并发安全的 hashmap,但是锁本身也会带来额外的性能开销,所以 Go1.9 开始标准库提供了并发安全的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sync.Map","attrs":{}}],"attrs":{}},{"type":"text","text":",使用起来也很简单,如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func main() {\n m := sync.Map{}\n\n // 添加元素\n m.Store(\"key\", \"value\")\n\n // 获取元素\n if value, ok := m.Load(\"key\"); ok {\n fmt.Println(value)\n }\n\n // 遍历\n m.Range(func(key, value interface{}) bool {\n fmt.Println(key, value)\n return true\n })\n \n // 并发读写\n go func() {\n for {\n m.Store(1, 1)\n }\n }()\n\n go func() {\n for {\n _, _ = m.Load(1)\n }\n }()\n\n time.Sleep(time.Second * 10)\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"sync.Map","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直接进入主题,看源码","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type Map struct {\n mu Mutex\n read atomic.Value // readOnly\n dirty map[interface{}]*entry\n misses int\n}\n\ntype readOnly struct {\n m map[interface{}]*entry\n amended bool \n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 只读数据 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"readOnly","attrs":{}}],"attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 读写数据,操作 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 需要用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mu","attrs":{}}],"attrs":{}},{"type":"text","text":" 进行加锁来保证并发安全","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"misses","attrs":{}}],"attrs":{}},{"type":"text","text":" 用于统计有多少次读取 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 没有命中","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"amended","attrs":{}}],"attrs":{}},{"type":"text","text":" 用于标记 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 的数据是否一致","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Load","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (m *Map) Load(key interface{}) (value interface{}, ok bool) {\n read, _ := m.read.Load().(readOnly)\n e, ok := read.m[key]\n if !ok && read.amended {\n m.mu.Lock()\n read, _ = m.read.Load().(readOnly)\n e, ok = read.m[key]\n if !ok && read.amended {\n e, ok = m.dirty[key]\n m.missLocked()\n }\n m.mu.Unlock()\n }\n if !ok {\n return nil, false\n }\n return e.load()\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Load","attrs":{}}],"attrs":{}},{"type":"text","text":" 的源码可以看出读取数据时,首先是从 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 读,没有命中的话会到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 读取数据,同时调用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"missLocked()","attrs":{}}],"attrs":{}},{"type":"text","text":" 增加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"misses","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (m *Map) missLocked() {\n m.misses++\n if m.misses < len(m.dirty) {\n return\n }\n m.read.Store(readOnly{m: m.dirty})\n m.dirty = nil\n m.misses = 0\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到当 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"misses","attrs":{}}],"attrs":{}},{"type":"text","text":" 大于 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"len(dirty)","attrs":{}}],"attrs":{}},{"type":"text","text":" 时则表示 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 的数据相差太大,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sync.Map","attrs":{}}],"attrs":{}},{"type":"text","text":" 会将 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 的数据赋值给 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":",而 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 会被置空","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Store","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (m *Map) Store(key, value interface{}) {\n read, _ := m.read.Load().(readOnly)\n if e, ok := read.m[key]; ok && e.tryStore(&value) {\n return\n }\n\n m.mu.Lock()\n read, _ = m.read.Load().(readOnly)\n if e, ok := read.m[key]; ok {\n if e.unexpungeLocked() {\n m.dirty[key] = e\n }\n e.storeLocked(&value)\n } else if e, ok := m.dirty[key]; ok {\n e.storeLocked(&value)\n } else {\n if !read.amended {\n m.dirtyLocked()\n m.read.Store(readOnly{m: read.m, amended: true})\n }\n m.dirty[key] = newEntry(value)\n }\n m.mu.Unlock()\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Store","attrs":{}}],"attrs":{}},{"type":"text","text":" 首先会直接到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 修改数据,修改成功则直接返回,如果 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"key","attrs":{}}],"attrs":{}},{"type":"text","text":" 不存在那么就表示要到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 找数据,如果 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 存在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"key","attrs":{}}],"attrs":{}},{"type":"text","text":" 则修改,如果不存在则新增,同时还要将 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"amended","attrs":{}}],"attrs":{}},{"type":"text","text":" 标记为 true,表示 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 的数据已经不一致了","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Range","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (m *Map) Range(f func(key, value interface{}) bool) {\n read, _ := m.read.Load().(readOnly)\n if read.amended {\n m.mu.Lock()\n read, _ = m.read.Load().(readOnly)\n if read.amended {\n read = readOnly{m: m.dirty}\n m.read.Store(read)\n m.dirty = nil\n m.misses = 0\n }\n m.mu.Unlock()\n }\n\n for k, e := range read.m {\n v, ok := e.load()\n if !ok {\n continue\n }\n if !f(k, v) {\n break\n }\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Range","attrs":{}}],"attrs":{}},{"type":"text","text":" 的源码没太多可以说的,有两点需要关注,一个是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Range","attrs":{}}],"attrs":{}},{"type":"text","text":" 会保证 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"read","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dirty","attrs":{}}],"attrs":{}},{"type":"text","text":" 是数据同步的,另一个是回调函数返回 false 会导致迭代中断","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8a/8a644c9dcb0d0d43323ddbb68f98429d.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章