go sync.Map使用和介紹
阿新 • • 發佈:2019-01-07
sync.Map使用和介紹
1、首先看下該sync.Map的使用:
package main
import (
"sync"
"fmt"
)
func main() {
//開箱即用
var sm sync.Map
//store 方法,新增元素
sm.Store(1,"a")
//Load 方法,獲得value
if v,ok:=sm.Load(1);ok{
fmt.Println(v)
}
//LoadOrStore方法,獲取或者儲存
//引數是一對key:value,如果該key存在且沒有被標記刪除則返回原先的value(不更新)和true;不存在則store,返回該value 和false
if vv,ok:=sm.LoadOrStore(1,"c");ok{
fmt.Println(vv)
}
if vv,ok:=sm.LoadOrStore(2,"c");!ok{
fmt.Println(vv)
}
//遍歷該map,引數是個函式,該函式參的兩個引數是遍歷獲得的key和value,返回一個bool值,當返回false時,遍歷立刻結束。
sm.Range(func(k,v interface{})bool{
fmt.Print(k)
fmt.Print(":")
fmt.Print(v)
fmt.Println()
return true
})
}
執行結果:
a
a
c
1:a
2:c
2、利用傳統的sync.RWMutex+Map 實現併發安全的map:
var rwmap = struct{
sync.RWMutex
m map[string]string
}{m: make(map[string]sring)}
讀資料時候,讀鎖鎖定:
rwmap.RLock()
value:= rwmap.m["key"]
rwmap.RUnlock()
fmt.Println("key:", value)
寫資料的時候,寫鎖鎖定:
rwmap.Lock()
rwmap.m ["key"]="value"
rwmap.Unlock()
3、兩種map的效能對比:
下面是有人做的兩種map效能對比圖:
可見隨著cpu核心數的增加、併發加劇,這種讀寫鎖+map的方式效能在不停的衰減,並且在核數為4的時候出現了效能的拐點;而sync.Map雖然效能不是特別好,但是相對比較平穩。
4、sync.Map 原始碼解析
原始碼位於:src\sync\map.go
首先檢視一下sync.Map的資料結構:
type Map struct {
// 該鎖用來保護dirty
mu Mutex
// 存讀的資料,因為是atomic.value型別,只讀型別,所以它的讀是併發安全的
read atomic.Value // readOnly
//包含最新的寫入的資料,並且在寫的時候,會把read 中未被刪除的資料拷貝到該dirty中,因為是普通的map存在併發安全問題,需要用到上面的mu欄位。
dirty map[interface{}]*entry
// 從read讀資料的時候,會將該欄位+1,當等於len(dirty)的時候,會將dirty拷貝到read中(從而提升讀的效能)。
misses int
}
read的資料結構是:
type readOnly struct {
m map[interface{}]*entry
// 如果Map.dirty的資料和m 中的資料不一樣是為true
amended bool
}
entry的資料結構:
type entry struct {
//可見value是個指標型別,雖然read和dirty存在冗餘情況(amended=false),但是由於是指標型別,儲存的空間應該不是問題
p unsafe.Pointer // *interface{}
}
Delete
首先來看delete方法
func (m *Map) Delete(key interface{}) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
//如果read中沒有,並且dirty中有新元素,那麼就去dirty中去找
if !ok && read.amended {
m.mu.Lock()
//這是雙檢查(上面的if判斷和鎖不是一個原子性操作)
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
//直接刪除
delete(m.dirty, key)
}
m.mu.Unlock()
}
if ok {
//如果read中存在該key,則將該value 賦值nil(採用標記的方式刪除!)
e.delete()
}
}
func (e *entry) delete() (hadValue bool) {
for {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return false
}
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
return true
}
}
}
Store
新加元素
func (m *Map) Store(key, value interface{}) {
// 如果m.read存在這個key,並且沒有被標記刪除,則嘗試更新。
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
// 如果read不存在或者已經被標記刪除
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
//如果entry被標記expunge,則表明dirty沒有key,可新增入dirty,並更新entry
if e.unexpungeLocked() {
//加入dirty中
m.dirty[key] = e
}
//更新value值
e.storeLocked(&value)
//dirty 存在該key,更新
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
//read 和dirty都沒有,新新增一條
} else {
//dirty中沒有新的資料,往dirty中增加第一個新鍵
if !read.amended {
//將read中未刪除的資料加入到dirty中
m.dirtyLocked()
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
//將read中未刪除的資料加入到dirty中
func (m *Map) dirtyLocked() {
if m.dirty != nil {
return
}
read, _ := m.read.Load().(readOnly)
m.dirty = make(map[interface{}]*entry, len(read.m))
//read如果較大的話,可能影響效能
for k, e := range read.m {
//通過此次操作,dirty中的元素都是未被刪除的,可見expunge的元素不在dirty中
if !e.tryExpungeLocked() {
m.dirty[k] = e
}
}
}
//判斷entry是否被標記刪除,並且將標記為nil的entry更新標記為expunge
func (e *entry) tryExpungeLocked() (isExpunged bool) {
p := atomic.LoadPointer(&e.p)
for p == nil {
// 將已經刪除標記為nil的資料標記為expunged
if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
return true
}
p = atomic.LoadPointer(&e.p)
}
return p == expunged
}
//對entry 嘗試更新
func (e *entry) tryStore(i *interface{}) bool {
p := atomic.LoadPointer(&e.p)
if p == expunged {
return false
}
for {
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
return true
}
p = atomic.LoadPointer(&e.p)
if p == expunged {
return false
}
}
}
//read裡 將標記為expunge的更新為nil
func (e *entry) unexpungeLocked() (wasExpunged bool) {
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}
//更新entry
func (e *entry) storeLocked(i *interface{}) {
atomic.StorePointer(&e.p, unsafe.Pointer(i))
}
可見,每次操作先檢查read,因為read 併發安全,效能好些;read不滿足,則加鎖檢查dirty,一旦是新的鍵值,dirty會被read更新。
Load
載入方法,查詢key。
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
//因read只讀,執行緒安全,先檢視是否滿足條件
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
//如果read沒有,並且dirty有新資料,那從dirty中查詢,由於dirty是普通map,執行緒不安全,這個時候用到互斥鎖了
if !ok && read.amended {
m.mu.Lock()
// 雙重檢查
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
// 如果read中還是不存在,並且dirty中有新資料
if !ok && read.amended {
e, ok = m.dirty[key]
// mssLocked()函式是效能是sync.Map 效能得以保證的重要函式,目的講有鎖的dirty資料,替換到只讀執行緒安全的read裡
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
return e.load()
}
//dirty 提升至read 關鍵函式,當misses 經過多次因為load之後,大小等於len(dirty)時候,講dirty替換到read裡,以此達到效能提升。
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
//原子操作,耗時很小
m.read.Store(readOnly{m: m.dirty})
m.dirty = nil
m.misses = 0
}
原始碼用的是1.9版本,通過閱讀原始碼我們發現sync.Map是通過冗餘的兩個資料結構(read、dirty),實現效能的提升。為了提升效能,load、delete、store等操作儘量使用只讀的read;為了提高read的key擊中概率,採用動態調整,將dirty資料提升為read;對於資料的刪除,採用延遲標記刪除法,只有在提升dirty的時候才刪除。