深入淺出 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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章