Golang:sync.Map
阿新 • • 發佈:2018-12-14
由於map在gorountine 上不是安全的,所以在大量併發讀寫的時候,會出現錯誤。
在1.9版的時候golang推出了sync.Map.
sync.Map
通過閱讀原始碼我們發現sync.Map是通過冗餘的兩個資料結構(read、dirty),實現效能的提升。
為了提升效能,load、delete、store等操作儘量使用只讀的read;
為了提高read的key命中概率,只有當read中讀取不到的累計miss次數大於等於dirty的長度時,將dirty資料提升為read;
對於資料的刪除,採用延遲標記刪除法,只有在提升dirty的時候才刪除。
資料結構
1 type Map struct{ 2 // 讀寫dirty時使用的鎖 3 mu Mutex 4 read atomic.Value 5 dirty map[interface{}]*entry 6 // 從read中讀取不到,從dirty讀取到資料時,+1 7 misses int 8 } 9 10 type readOnly struct { 11 m map[interface{}]*entry 13 amended bool 14 } 15 16 type entry struct { 17 //指標型別 18 p unsafe.Pointer 19 }
Delete
1 func (m *Map) Delete(key interface{}) { 2 read, _ := m.read.Load().(readOnly) 3 e, ok := read.m[key] 4 if !ok && read.amended { 5 m.mu.Lock() 6 read, _ = m.read.Load().(readOnly) 7 e, ok = read.m[key] 8 if!ok && read.amended { //double check 9 delete(m.dirty, key) 10 } 11 m.mu.Unlock() 12 } 13 if ok { 14 e.delete() 15 } 16 }
1 func (e *entry) delete() (hadValue bool) { 2 for { 3 p := atomic.LoadPointer(&e.p) 4 if p == nil || p == expunged { 5 return false 6 } 7 if atomic.CompareAndSwapPointer(&e.p, p, nil) { //原子操作,加刪除標記 8 return true 9 } 10 } 11 }
刪除時,如果read中沒有,就直接從dirty刪除。如果read中有,就把read中標記為刪除。
Load
1 func (m *Map) Load(key interface{}) (value interface{}, ok bool) { 2 read, _ := m.read.Load().(readOnly) 3 e, ok := read.m[key] 4 if !ok && read.amended { 5 m.mu.Lock() 6 read, _ = m.read.Load().(readOnly) 7 e, ok = read.m[key] 8 if !ok && read.amended { 9 e, ok = m.dirty[key] // read中讀取不到,從dirty讀,miss++ 10 m.missLocked() 11 } 12 m.mu.Unlock() 13 } 14 if !ok { 15 return nil, false 16 } 17 return e.load() 18 }
Load返回儲存在對映中的鍵值(read中讀取不到,從dirty讀),如果沒有值,則返回nil。ok結果指示是否在對映中找到值。
missLocked和Store
1 func (m *Map) missLocked() { 2 m.misses++ 3 if m.misses < len(m.dirty) { 4 return 5 } 6 m.read.Store(readOnly{m: m.dirty}) 7 m.dirty = nil 8 m.misses = 0 9 }
1 func (m *Map) Store(key, value interface{}) { 2 //如果在read 讀取到,就原子操作直接對值進行更新 3 read, _ := m.read.Load().(readOnly) 4 if e, ok := read.m[key]; ok && e.tryStore(&value) { 5 return 6 } 7 8 //如果未在read 中讀取到值或讀取到值進行更新時更新失敗,則加鎖進行後續處理 9 m.mu.Lock() 10 read, _ = m.read.Load().(readOnly) 11 if e, ok := read.m[key]; ok { 12 // double check,如果讀取到的值處於刪除狀態,將值寫入dirty map中 13 if e.unexpungeLocked() { 14 m.dirty[key] = e 15 } 16 // 原子操作更新key對應的值 17 e.storeLocked(&value) 18 } else if e, ok := m.dirty[key]; ok { 19 //如果在dirty map中讀取到值,則直接使用原子操作更新值 20 e.storeLocked(&value) 21 } else { 22 //如果dirty map中不含有值,則說明dirty map已經升級為read map,或者第一次進入 23 //需要初始化dirty map,並將read map的key新增到新建立的dirty map中. 24 if !read.amended { 25 m.dirtyLocked() 26 m.read.Store(readOnly{m: read.m, amended: true}) 27 } 28 m.dirty[key] = newEntry(value) 29 } 30 m.mu.Unlock() 31 }
一些觀點,當有大量併發讀寫發生的時候,會有很多的miss導致不斷的dirty升級。可能會影響效率。
嘗試使用sync.Map
1 package main 2 3 import ( 4 "fmt" 5 "sync" 6 ) 7 8 func main() { 9 var m sync.Map 10 m.Store(1, "a") 11 m.Store(2, "b") 12 m.Store(3, "c") 13 m.Store(4, "d") 14 15 m.Range(func(k, v interface{}) bool { 16 fmt.Println(k, v) 17 return true 18 }) 19 //LoadOrStore 20 v, ok := m.LoadOrStore(5, "e") 21 fmt.Println(ok, v) 22 23 v, ok = m.LoadOrStore(1, "bbb") 24 fmt.Println(ok, v) 25 26 //Load 27 v, ok = m.Load(1) 28 if ok { 29 fmt.Println("it's an existing key,value is ", v) 30 } else { 31 fmt.Println("it's an unknown key") 32 } 33 34 m.Range(func(k, v interface{}) bool { 35 fmt.Println(k, v) 36 return true 37 }) 38 39 m.Delete(1) 40 fmt.Println(m.Load(1)) 41 42 }
1 4 d 2 1 a 3 2 b 4 3 c 5 false e 6 true a 7 it's an existing key,value is a 8 2 b 9 3 c 10 4 d 11 5 e 12 1 a 13 <nil> false
執行結果是: