Go 1.9 sync Map 原始碼閱讀筆記
一、sync Map 包整體結構
本文主要闡述:Load、Store、Delete,更加詳細的闡述可以參考原始碼描述(建議先大體瀏覽一下Map原始碼)。
導言:
- 空間換時間。 通過冗餘的兩個資料結構(read、dirty),實現加鎖對效能的影響。
- 使用只讀資料(read),避免讀寫衝突。
- 動態調整,miss次數多了之後,將dirty資料提升為read。
- double-checking。
- 延遲刪除。 刪除一個鍵值只是打標記(會將key對應value的pointer置為nil,但read中仍然有這個key:key;value:nil的鍵值對),只有在提升dirty的時候才清理刪除的資料。
- 優先從read讀取、更新、刪除,因為對read的讀取不需要鎖。
- 雖然read和dirty有冗餘資料,但這些資料是通過指標指向同一個資料,所以儘管Map的value會很大,但是冗餘的空間佔用還是有限的。
二、基礎資料結構
1、Map
// Map is a concurrent map with amortized-constant-time loads, stores, and deletes.
// It is safe for multiple goroutines to call a Map's methods concurrently.
//
// It is optimized for use in concurrent loops with keys that are
// stable over time, and either few steady-state stores, or stores
// localized to one goroutine per key.
//
// For use cases that do not share these attributes, it will likely have
// comparable or worse performance and worse type safety than an ordinary
// map paired with a read-write mutex.
//
// The zero Map is valid and empty.
//
// A Map must not be copied after first use.
//該 Map 是執行緒安全的,讀取,插入,刪除也都保持著常數級的時間複雜度。
//多個 goroutines 協程同時呼叫 Map 方法也是執行緒安全的。該 Map 的零值是有效的,
//並且零值是一個空的 Map 。執行緒安全的 Map 在第一次使用之後,不允許被拷貝。
type Map struct {
mu Mutex
// read contains the portion of the map's contents that are safe for
// concurrent access (with or without mu held).
//
// The read field itself is always safe to load, but must only be stored with
// mu held.
//
// Entries stored in read may be updated concurrently without mu, but updating
// a previously-expunged entry requires that the entry be copied to the dirty
// map and unexpunged with mu held.
// 一個只讀的資料結構,因為只讀,所以不會有讀寫衝突。
// 所以從這個資料中讀取總是安全的。
// 實際上,實際也會更新這個資料的entries,如果entry是未刪除的(unexpunged), 並不需要加鎖。如果entry已經被刪除了,需要加鎖,以便更新dirty資料。
read atomic.Value // readOnly
// dirty contains the portion of the map's contents that require mu to be
// held. To ensure that the dirty map can be promoted to the read map quickly,
// it also includes all of the non-expunged entries in the read map.
//
// Expunged entries are not stored in the dirty map. An expunged entry in the
// clean map must be unexpunged and added to the dirty map before a new value
// can be stored to it.
//
// If the dirty map is nil, the next write to the map will initialize it by
// making a shallow copy of the clean map, omitting stale entries.
// dirty資料包含當前的map包含的entries,它包含最新的entries(包括read中未刪除的資料,雖有冗餘,但是提升dirty欄位為read的時候非常快,不用一個一個的複製,而是直接將這個資料結構作為read欄位的一部分),有些資料還可能沒有移動到read欄位中。
// 對於dirty的操作需要加鎖,因為對它的操作可能會有讀寫競爭。
// 當dirty為空的時候, 比如初始化或者剛提升完,下一次的寫操作會複製read欄位中未刪除的資料到這個資料中。
dirty map[interface{}]*entry
// misses counts the number of loads since the read map was last updated that
// needed to lock mu to determine whether the key was present.
//
// Once enough misses have occurred to cover the cost of copying the dirty
// map, the dirty map will be promoted to the read map (in the unamended
// state) and the next store to the map will make a new dirty copy.
// 當從Map中讀取entry的時候,如果read中不包含這個entry,會嘗試從dirty中讀取,這個時候會將misses加一,
// 當misses累積到 dirty的長度的時候, 就會將dirty提升為read,避免從dirty中miss太多次。因為操作dirty需要加鎖。
misses int
}
2、readOnly
// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
m map[interface{}]*entry
// true if the dirty map contains some key not in m.
// 如果Map.dirty有些資料不在中的時候,這個值為true
amended bool
}
3、entry
// An entry is a slot in the map corresponding to a particular key.
type entry struct {
// p points to the interface{} value stored for the entry.
//
// If p == nil, the entry has been deleted and m.dirty == nil.
//
// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
// is missing from m.dirty.
//
// Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
// != nil, in m.dirty[key].
//
// An entry can be deleted by atomic replacement with nil: when m.dirty is
// next created, it will atomically replace nil with expunged and leave
// m.dirty[key] unset.
//
// An entry's associated value can be updated by atomic replacement, provided
// p != expunged. If p == expunged, an entry's associated value can be updated
// only after first setting m.dirty[key] = e so that lookups using the dirty
// map find the entry.
//p有三種值:
//nil: entry已被刪除了,並且m.dirty為nil
//expunged: entry已被刪除了,並且m.dirty不為nil,而且這個entry不存在於m.dirty中
//其它: entry是一個正常的值
p unsafe.Pointer // *interface{}
}
4、Value
// A Value provides an atomic load and store of a consistently typed value.
// Values can be created as part of other data structures.
// The zero value for a Value returns nil from Load.
// Once Store has been called, a Value must not be copied.
//
// A Value must not be copied after first use.
type Value struct {
noCopy noCopy
v interface{}
}
下圖來自:http://www.jianshu.com/p/43e66dab535b
三、Load
根據指定的key,查詢對應的值value,如果不存在,通過ok反映
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果沒找到,並且m.dirty中有新資料,需要從m.dirty查詢,這個時候需要加鎖
if !ok && read.amended {
m.mu.Lock()
// Avoid reporting a spurious miss if m.dirty got promoted while we were
// blocked on m.mu. (If further loads of the same key will not miss, it's
// not worth copying the dirty map for this key.)
//double check,避免加鎖的時候m.dirty提升為m.read,這個時候m.read可能被替換了。
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
// Regardless of whether the entry was present, record a miss: this key
// will take the slow path until the dirty map is promoted to the read
// map.
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
return e.load()
}
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
}
四、Store
更新或者新增一個entry
// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
read, _ := m.read.Load().(readOnly)
// 從 read map 中讀取 key 成功並且取出的 entry 嘗試儲存 value 成功,直接返回
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {//確保未被標記成刪除,即e 指向的是非 nil 的
// The entry was previously expunged, which implies that there is a
// non-nil dirty map and this entry is not in it.
//m.dirty中不存在這個鍵,所以加入m.dirty
m.dirty[key] = e
}
e.storeLocked(&value)
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
} else {
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
// tryStore stores a value if the entry has not been expunged.
//
// If the entry is expunged, tryStore returns false and leaves the entry
// unchanged.
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
}
}
}
func (m *Map) dirtyLocked() {
if m.dirty != nil {
return
}
read, _ := m.read.Load().(readOnly)
m.dirty = make(map[interface{}]*entry, len(read.m))
for k, e := range read.m {
if !e.tryExpungeLocked() {
m.dirty[k] = e
}
}
}
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
}
// unexpungeLocked ensures that the entry is not marked as expunged.
// If the entry was previously expunged, it must be added to the dirty map
// before m.mu is unlocked.
// unexpungeLocked 函式確保了 entry 沒有被標記成已被清除。
// 如果 entry 先前被清除過了,那麼在 mutex 解鎖之前,它一定要被加入到 dirty map 中
//如果 entry 的 unexpungeLocked 返回為 true,那麼就說明 entry
//之前被標記成了 expunged,並經過 CAS 操作成功把它置為 nil。
func (e *entry) unexpungeLocked() (wasExpunged bool) {
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}
五、Delete
刪除一個鍵值
// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
delete(m.dirty, key)
}
m.mu.Unlock()
}
if ok {
e.delete()
}
}
func (e *entry) delete() (hadValue bool) {
for {
p := atomic.LoadPointer(&e.p)
// 已標記為刪除
if p == nil || p == expunged {
return false
}
// 原子操作,e.p標記為nil
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
return true
}
}
}
六、疑問
1、已經刪除的key,再次Load的時候,會怎麼樣?
func (e *entry) load() (value interface{}, ok bool) {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return nil, false
}
return *(*interface{})(p), true
}
在Map Load方法中呼叫e.load()時,load方法會識別該值是否已被刪除
本文map結構描述部分參考:https://studygolang.com/articles/10511
Java 1.8 ConcurrentHashMap 原始碼註解部分:
https://github.com/jiankunking/backups/blob/master/ConcurrentHashMap.java
個人微信公眾號:
作者:jiankunking 出處:http://blog.csdn.net/jiankunking
相關文章
- Go 1.9 sync.MapGo
- Go1.9sync.MapGo
- go sync.Map原始碼分析Go原始碼
- sync.pool 原始碼閱讀原始碼
- Go語言——sync.Map原始碼分析Go原始碼
- ArrayList原始碼閱讀筆記原始碼筆記
- CopyOnWriteArrayList原始碼閱讀筆記原始碼筆記
- Koa 原始碼閱讀筆記原始碼筆記
- memcached 原始碼閱讀筆記原始碼筆記
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- JDK原始碼閱讀(5):HashTable類閱讀筆記JDK原始碼筆記
- JDK原始碼閱讀(4):HashMap類閱讀筆記JDK原始碼HashMap筆記
- JDK原始碼閱讀:String類閱讀筆記JDK原始碼筆記
- LinkedList原始碼閱讀筆記原始碼筆記
- Express Session 原始碼閱讀筆記ExpressSession原始碼筆記
- guavacache原始碼閱讀筆記Guava原始碼筆記
- Tomcat原始碼閱讀筆記Tomcat原始碼筆記
- python原始碼閱讀筆記Python原始碼筆記
- sync.Map原始碼分析原始碼
- JDK原始碼閱讀(7):ConcurrentHashMap類閱讀筆記JDK原始碼HashMap筆記
- go中sync.Mutex原始碼解讀GoMutex原始碼
- goroutine排程原始碼閱讀筆記Go原始碼筆記
- Flask 原始碼閱讀筆記 開篇Flask原始碼筆記
- 【iOS印象】GLPubSub 原始碼閱讀筆記iOS原始碼筆記
- LongAdder原始碼閱讀筆記原始碼筆記
- Redux 學習筆記 – 原始碼閱讀Redux筆記原始碼
- 走進原始碼——ArrayList閱讀筆記原始碼筆記
- 走進原始碼——Vector閱讀筆記原始碼筆記
- 走進原始碼——CopyOnWriteArrayList閱讀筆記原始碼筆記
- Redux 學習筆記 - 原始碼閱讀Redux筆記原始碼
- Laravel 訊息通知原始碼閱讀筆記Laravel原始碼筆記
- ObjC runtime原始碼 閱讀筆記(一)OBJ原始碼筆記
- 學習筆記 sync/RWMutex原始碼筆記Mutex原始碼
- 【初學】Spring原始碼筆記之零:閱讀原始碼Spring原始碼筆記
- go 中 select 原始碼閱讀Go原始碼
- Django 原始碼閱讀筆記(基礎檢視)Django原始碼筆記
- Swift標準庫原始碼閱讀筆記 - DictionarySwift原始碼筆記
- tomcat8.5.57原始碼閱讀筆記1Tomcat原始碼筆記