go sync.Map源碼分析

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

概述

go 語言中的map並不是並發安全的,在Go 1.6之前,並發讀寫map會導致讀取到髒資料,在1.6之後則程式直接panic. 因此之前的解決方案一般都是通過引入RWMutex(讀寫鎖)進行處理,
關於go為什麼支援map的原子操作,概況來說,對map原子操作一定程度上降低了只有並發讀,或不存在並發讀寫等情境的效能.
但作為服務端來說,使用go編寫服務之後,大部分情況下都會存在gorutine並發訪問map的情況,因此,1.9之後,go 在sync包下引入了並發安全的map.
這裡將從源碼對其進行解讀.

1. sync.Map提供的方法

  • 儲存資料,存入key以及value可以為任意類型.
func (m *Map) Store(key, value interface{})
  • 刪除對應key
func (m *Map) Delete(key interface{})
  • 讀取對應key的值,ok表示是否在map中查詢到key
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
  • 針對某個key的存在讀取不存在就儲存,loaded為true表示存在值,false表示不存在值.
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
  • 表示對所有key進行遍曆,並將遍曆出的key,value傳入回呼函數進行函數調用,回呼函數返回false時遍曆結束,否則遍曆完所有key.
func (m *Map) Range(f func(key, value interface{}) bool)

2. 原理

通過引入兩個map,將讀寫分離到不同的map,其中read map只提供讀,而dirty map則負責寫.
這樣read map就可以在不加鎖的情況下進行並發讀取,當read map中沒有讀取到值時,再加鎖進行後續讀取,並累加未命中數,當未命中數到達一定數量後,將dirty map上升為read map.

另外,雖然引入了兩個map,但是底層資料存放區的是指標,指向的是同一份值.

具體流程:
如插入key 1,2,3時均插入了dirty map中,此時read map沒有key值,讀取時從dirty map中讀取,並記錄miss數

當miss數大於等於dirty map的長度時,將dirty map直接升級為read map,這裡直接 對dirty map進行地址拷貝.

當有新的key 4插入時,將read map中的key值拷貝到dirty map中,這樣dirty map就含有所有的值,下次升級為read map時直接進行地址拷貝.

3. 源碼分析

3.1 主要結構

entry結構,用於儲存value的interface指標,通過atomic進行原子操作.

type entry struct {    p unsafe.Pointer // *interface{}}

Map結構, 主結構,提供對外的方法,以及資料存放區.

type Map struct {    mu Mutex        //儲存readOnly,不加鎖的情況下,對其進行並發讀取    read atomic.Value // readOnly    //dirty map用於儲存寫入的資料,能直接升級成read map.    dirty map[interface{}]*entry    //misses 主要記錄read讀取不到資料加鎖讀取read map以及dirty map的次數.    misses int}

readOnly 結構, 主要用於儲存

// readOnly 通過原子操作儲存在Map.read中, type readOnly struct {    m       map[interface{}]*entry    amended bool // true if the dirty map contains some key not in m.}

3.1 Load方法

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {    read, _ := m.read.Load().(readOnly)    e, ok := read.m[key]    if !ok && read.amended {        m.mu.Lock()        //加鎖,然後再讀取一遍read map中內容,主要防止在加鎖的過程中,dirty map轉換成read map,從而導致讀取不到資料.        read, _ = m.read.Load().(readOnly)        e, ok = read.m[key]        if !ok && read.amended {            e, ok = m.dirty[key]            //記錄miss數, 在dirty map提升為read map之前,            //這個key值都必須在加鎖的情況下在dirty map中讀取到.            m.missLocked()        }        m.mu.Unlock()    }    if !ok {        return nil, false    }    return e.load()}

3.2 Store方法

// Store sets the value for a key.func (m *Map) Store(key, value interface{}) {    //如果在read map讀取到值,則嘗試使用原子操作直接對值進行更新,更新成功則返回    read, _ := m.read.Load().(readOnly)    if e, ok := read.m[key]; ok && e.tryStore(&value) {        return    }    //如果未在read map中讀取到值或讀取到值進行更新時更新失敗,則加鎖進行後續處理    m.mu.Lock()    read, _ = m.read.Load().(readOnly)    if e, ok := read.m[key]; ok {        //在檢查一遍read,如果讀取到的值處於刪除狀態,將值寫入dirty map中        if e.unexpungeLocked() {            m.dirty[key] = e        }        //使用原子操作更新key對應的值        e.storeLocked(&value)    } else if e, ok := m.dirty[key]; ok {        //如果在dirty map中讀取到值,則直接使用原子操作更新值        e.storeLocked(&value)    } else {        //如果dirty map中不含有值,則說明dirty map已經升級為read map,或者第一次進入        //需要初始化dirty map,並將read map的key添加到新建立的dirty map中.        if !read.amended {            m.dirtyLocked()            m.read.Store(readOnly{m: read.m, amended: true})        }        m.dirty[key] = newEntry(value)    }    m.mu.Unlock()}

3.3 LoadOrStore方法

代碼邏輯和Store類似

func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {    // 不加鎖的情況下讀取read map    read, _ := m.read.Load().(readOnly)    if e, ok := read.m[key]; ok {        //如果讀取到值則嘗試對值進行更新或讀取        actual, loaded, ok := e.tryLoadOrStore(value)        if ok {            return actual, loaded        }    }    m.mu.Lock()    read, _ = m.read.Load().(readOnly)    // 在加鎖的請求下在確定一次read map    if e, ok := read.m[key]; ok {        if e.unexpungeLocked() {            m.dirty[key] = e        }        actual, loaded, _ = e.tryLoadOrStore(value)    } else if e, ok := m.dirty[key]; ok {        actual, loaded, _ = e.tryLoadOrStore(value)        m.missLocked()    } else {        if !read.amended {            m.dirtyLocked()            m.read.Store(readOnly{m: read.m, amended: true})        }        m.dirty[key] = newEntry(value)        actual, loaded = value, false    }    m.mu.Unlock()    return actual, loaded}

3.4 Range 方法

func (m *Map) Range(f func(key, value interface{}) bool) {    //先擷取read map中值    read, _ := m.read.Load().(readOnly)    //如果dirty map中還有值,則進行加鎖檢測    if read.amended {        m.mu.Lock()        read, _ = m.read.Load().(readOnly)                if read.amended {            //將dirty map中賦給read,因為dirty map包含了所有的值            read = readOnly{m: m.dirty}            m.read.Store(read)            m.dirty = nil            m.misses = 0        }        m.mu.Unlock()    }    //進行遍曆    for k, e := range read.m {        v, ok := e.load()        if !ok {            continue        }        if !f(k, v) {            break        }    }}

3.5 Delete 方法

func (m *Map) Delete(key interface{}) {    //首先擷取read map    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]        //沒有在read map中擷取到值,到dirty map中刪除        if !ok && read.amended {            delete(m.dirty, key)        }        m.mu.Unlock()    }    if ok {        e.delete()    }}

4. 局限性

從以上的源碼可知,sync.map並不適合約時存在大量讀寫的情境,大量的寫會導致read map讀取不到資料從而加鎖進行進一步讀取,同時dirty map不斷升級為read map.
從而導致整體效能較低,特別是針對cache情境.針對append-only以及大量讀,少量寫情境使用sync.map則相對比較合適.

對於map,還有一種基於hash的實現思路,具體就是對map加讀寫鎖,但是分配n個map,根據對key做hash運算確定是分配到哪個map中.
這樣鎖的消耗就降到了1/n(理論值).具體實現可見:https://github.com/orcaman/co...

相比之下, 基於hash的方式更容易理解,整體效能較穩定. sync.map在某些情境效能可能差一些,但某些情境卻能取得更好的效果.
所以還是要根據具體的業務情境進行取捨.

5. 參考

https://golang.org/doc/faq#at...
https://github.com/golang/go/...

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.