sync.Map源碼分析

來源:互聯網
上載者:User
## [部落格地址:sync.Map源碼分析](https://github.com/Chasiny/Blog/blob/master/blog/go/sync.Map%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md)## 普通的mapgo普通的map是不支援並發的,例如簡單的寫```gofunc main() {wg := sync.WaitGroup{}wg.Add(10)m := make(map[int]int)for i := 0; i < 10; i++ {go func(i int) {m[i] = iwg.Done()}(i)}wg.Wait()}``````bashfatal error: concurrent map writes```## sync.Mapgo的sync.Map幾個最佳化點* 通過使用優先讀的結構體read減少鎖的衝突* 使用雙重檢測* 使用延遲刪除(刪除存在於read中的資料只是將其置為nil)* 動態調整,miss次數多了之後,將dirty資料提升為read從sync/map.go看Map的結構體```gotype Map struct {mu Mutex //互斥鎖,用於鎖定dirty mapread atomic.Value //讀map,實際上不是唯讀,是優先讀dirty map[interface{}]*entry //dirty是一個當前最新的map,允許讀寫misses int //標記在read中沒有命中的次數,當misses等於dirty的長度時,會將dirty複製到read}//read儲存的實際結構體type readOnly struct {m map[interface{}]*entry //mapamended bool //如果有些資料在dirty中但沒有在read中,該值為true}type entry struct {p unsafe.Pointer //資料指標}```### entry的幾種類型* nil: 表示為被刪除,此時read跟dirty同時有該鍵(一般該索引值如果存在於read中,則刪除是將其標記為nil)* expunged: 也是表示被刪除,但是該鍵只在read而沒有在dirty中,這種情況出現在將read複製到dirty中,即複製的過程會先將nil標記為expunged,然後不將其複製到dirty* 其他: 表示存著真正的資料## sync.Map幾種方法: 首先先說明read跟dirty不是直接存對象,而是存指標,這樣的話如果索引值同時存在在read跟dirty中,直接原子修改read也相當於修改dirty中的值,並且當read跟dirty存在大量相同的資料時,也不會使用太多的記憶體### Load```gofunc (m *Map) Load(key interface{}) (value interface{}, ok bool) {read, _ := m.read.Load().(readOnly)e, ok := read.m[key]if !ok && read.amended { //如果不在read中,並且dirty有新資料,則從dirty拿 m.mu.Lock() //雙重檢查,因為有可能在加鎖前read剛好插入該值read, _ = m.read.Load().(readOnly)e, ok = read.m[key]if !ok && read.amended { //沒有在read中,則從dirty拿e, ok = m.dirty[key]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 } //當沒有命中的次數等於dirty的大小,將dirty複製給readm.read.Store(readOnly{m: m.dirty})m.dirty = nilm.misses = 0}```read主要用於讀取,每次Load都先從read讀取,當read中不存在且amended為true,就從dirty讀取資料 無論dirty是否存在該key,都會執行missLocked函數,該函數將misses+1,當misses等於dirty的大小時,便會將dirty複製到read,此時再將dirty置為nil ### Delete```gofunc (m *Map) Delete(key interface{}) {read, _ := m.read.Load().(readOnly)e, ok := read.m[key]if !ok && read.amended { //如果不在read中,並且dirty有新資料,則從dirty中找 m.mu.Lock() //雙重檢查read, _ = m.read.Load().(readOnly)e, ok = read.m[key]if !ok && read.amended { //這是表示索引值只存在於dirty,直接刪除dirty中的索引值即可delete(m.dirty, key)}m.mu.Unlock() } if ok { //如果在read中,則將其標記為刪除(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}}}```先判斷是否在read中,不在的話再從dirty刪除### Store```gofunc (m *Map) Store(key, value interface{}) { //如果read存在這個鍵,並且這個entry沒有被標記刪除,嘗試直接寫入//dirty也指向這個entry,所以修改e也可以使dirty也保持最新的entryread, _ := m.read.Load().(readOnly)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 { //該索引值存在在read中if e.unexpungeLocked() { //該索引值在read中被標記為抹除,則將其添加到dirtym.dirty[key] = e } //更新entry e.storeLocked(&value) } else if e, ok := m.dirty[key]; ok { //如果不在read中,在dirty中,則更新 e.storeLocked(&value) } else { //既不在read中,也不在dirty中if !read.amended { //從read複製沒有標記刪除的資料到dirty中m.dirtyLocked()m.read.Store(readOnly{m: read.m, amended: true}) } //添加到dirty中m.dirty[key] = newEntry(value)}m.mu.Unlock()}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 (e *entry) unexpungeLocked() (wasExpunged bool) {return atomic.CompareAndSwapPointer(&e.p, expunged, nil)}func (e *entry) storeLocked(i *interface{}) {atomic.StorePointer(&e.p, unsafe.Pointer(i))}func (m *Map) dirtyLocked() {if m.dirty != nil {return} //從read複製到dirtyread, _ := m.read.Load().(readOnly)m.dirty = make(map[interface{}]*entry, len(read.m))for k, e := range read.m { //如果標記為nil或者expunged,則不複製到dirtyif !e.tryExpungeLocked() {m.dirty[k] = e}}}func (e *entry) tryExpungeLocked() (isExpunged bool) {p := atomic.LoadPointer(&e.p)for p == nil { //嘗試將nil置為expungedif atomic.CompareAndSwapPointer(&e.p, nil, expunged) {return true}p = atomic.LoadPointer(&e.p)}return p == expunged}```sync.Map 寫入就稍微麻煩很多了 1. 首先會先判斷索引值是否已經存在read中,存在的話便嘗試直接寫入(read不只是讀,此時被寫入),由於從read擷取的是entry指標,因此對從read讀取entry進行修改便相當於修改dirty中對應的entry,此時寫入的是使用原子操作。2. 索引值存在在read中並且該entry被標記為expunged(這種情況出現在從read複製資料到dirty中,看tryExpungeLocked函數,將所有鍵為nil置為expunged,表示該鍵被刪除,但沒有在dirty中)3. 從read複製到dirty的過程來說,主要是用dirtyLocked函數實現的,複製除了entry為nil跟expunged的資料---參考* [Go sync.Map](https://github.com/golang/go/blob/master/src/sync/map.go)* [Go 1.9 sync.Map揭秘](http://colobu.com/2017/07/11/dive-into-sync-Map/)109 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.