1. 程式人生 > >Golang:sync.Map

Golang:sync.Map

由於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

 

執行結果是: