日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

go sync.map 源码分析

發(fā)布時間:2024/2/28 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 go sync.map 源码分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一 簡言

1.1?文件所在目錄:go/src/sync/map.go

1.2 本篇博客的go版本:go1.10.3

二 原理說明

  • 空間換時間。通過冗余的兩個數(shù)據(jù)結(jié)構(gòu)(read、dirty),實(shí)現(xiàn)加鎖對性能的影響。
  • 使用只讀數(shù)據(jù)(read),避免讀寫沖突。
  • 動態(tài)調(diào)整,miss次數(shù)多了之后,將dirty數(shù)據(jù)提升為read。
  • double-checking(雙重檢測)。
  • 延遲刪除。 刪除一個鍵值只是打標(biāo)記,只有在提升dirty的時候才清理刪除的數(shù)據(jù)。
  • 優(yōu)先從read讀取、更新、刪除,因?yàn)閷ead的讀取不需要鎖。
  • read并非僅僅只讀,其實(shí)更新,刪除都是在read中操作的
  • 二 ?簡單使用

    三 源碼分析

    基本結(jié)構(gòu)體的定義

    // Map就像一個map[interface{}]interface{},但是是并發(fā)安全的 // 針對兩種情況進(jìn)行了優(yōu)化 // 1. 只寫入一次,但多次讀取 // 2. 多協(xié)程讀取,寫入,覆蓋不同的key type Map struct {// 鎖,訪問dirty時才會加鎖mu Mutex// read 包含了map中可以安全并發(fā)訪問的部分(使用或者不使用mu鎖)// read 可以總是安全的load,但是store時必須和mu一起使用// read中的entry可以不加鎖,并發(fā)地更新,但是想要更新一個之前被置為expunged的entry,那么必須加入到dirty中,保持和read一致read atomic.Value // 下面的readOnly對象// dirty包含了map中需加鎖才能訪問的key// 被置為enxgunged的entry不會保存在dirty中// 若dirty為nil,read中的amended為false;若dirty不為nil,read中的amended為truedirty map[interface{}]*entry// misses 統(tǒng)計了read中讀取key不成功,需加鎖后判斷dirty中是否存在key的次數(shù)// 一旦未命中次數(shù)和dirty長度一樣時,平均每個key未命中一次,也就是說加鎖消耗達(dá)到了拷貝一次整個dirty,就把dirty提升為readmisses int }type readOnly struct {m map[interface{}]*entryamended bool // true表dirty中包含read中不存在的key }// expunged 是一個隨意的指針,用來標(biāo)記dirty map中的一個entry已經(jīng)被刪除,read中一個entry被刪除時,是置為nil var expunged = unsafe.Pointer(new(interface{}))type entry struct {// p指向存儲的interface值// 若p為nil,表明該entry在read中被刪除了,但dirty中還在,所以能直接更新值// 若p為expunged,表明該entry不存在于dirty中(因?yàn)橹挥衐irty新建時,p才有可能被置為expunged,具體見tryExpungeLocked函數(shù)),所以更新時要把這個entry復(fù)制到dirty中// 其他情況,該entry有效,且記錄在read中,若dirty不為空,那么dirty也存在該entry// 一個entry可以通過使用原子操作替換為nil來刪除// 一個entry對應(yīng)的值,可以通過原子操作來替換// 若p為expunged,一個entry的對應(yīng)值可以被更新,只有在第一次設(shè)置m.dirty[key] = e后p unsafe.Pointer // *interface{} }// 為i(其實(shí)是key對應(yīng)的value值)新建一個entry,其中p保存的是i的地址 func newEntry(i interface{}) *entry {return &entry{p: unsafe.Pointer(&i)} }

    3.1 新建,修改(Store(),LoadOrStore()),源碼及分析

    // 存儲key,value func (m *Map) Store(key, value interface{}) {// 先從read中讀取,存在則嘗試存儲,成功則返回read, _ := m.read.Load().(readOnly)if e, ok := read.m[key]; ok && e.tryStore(&value) {return}// read中不存在key,或者嘗試存儲時失敗,加鎖m.mu.Lock()// 雙重檢測(因?yàn)榧渔i過程中,dirty可能被提升為read,dirty被置為nil,此時read中可能存在該key了)read, _ = m.read.Load().(readOnly)if e, ok := read.m[key]; ok { // 若read中存在該key// 若被標(biāo)記為expunged(函數(shù)內(nèi)已更改為nil),說明dirty中不存在該entry(因?yàn)閑.p置為expunged只有一處,即dirty時新建/重建時調(diào)用的tryExpungeLocked函數(shù),并未添加到dirty中),所以此時需在dirty中添加該keyif e.unexpungeLocked() {m.dirty[key] = e}// 修改entry的值,注意:修改后read,dirty中都被修改了,因?yàn)閞ead,dirty中保存的時entry的指針,這里是通過指針修改的e.storeLocked(&value)} else if e, ok := m.dirty[key]; ok { // dirty中存在了,則更新dirty中e.storeLocked(&value)} else { // dirty中也沒有該key// 若dirty中不包含read中不存在的key,說明dirty還未新建或重建,此時應(yīng)創(chuàng)建dirtyif !read.amended {// 新建dirtym.dirtyLocked()// 新建read,并把a(bǔ)mended標(biāo)記為true,因此添加該key后,dirty中就包含了read中不包含的key了m.read.Store(readOnly{m: read.m, amended: true})}// dirty中為該key新建一個entry條目m.dirty[key] = newEntry(value)}m.mu.Unlock() }// 為該條目嘗試存儲值i(多協(xié)程操作,e.p的值可能有多種情況,所以需for循環(huán)使用CAS) // 若已刪除,直接返回false // 若未刪除,則循環(huán)使用CAS設(shè)置值;即使為nil,也可以賦值 func (e *entry) tryStore(i *interface{}) bool {p := atomic.LoadPointer(&e.p)// 若已刪除,則直接返回if p == expunged {return false}// 未刪除,則CAS操作,設(shè)置該值for {// CAS賦值if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {return true}// 設(shè)置失敗,則重新獲取值p = atomic.LoadPointer(&e.p)// 若已刪除,則說明其他協(xié)程剛剛刪除了該值,這里不可再設(shè)置if p == expunged {return false}} }// 為該entry無條件存儲值i(此時mu鎖是鎖定的,函數(shù)Locked結(jié)尾即標(biāo)明;調(diào)用時e.p不能是expunged,使用原子操作賦值) func (e *entry) storeLocked(i *interface{}) {atomic.StorePointer(&e.p, unsafe.Pointer(i)) }// unexpungeLocked 判斷本entry是否為expunged狀態(tài)(此時mu鎖是鎖定的,判斷規(guī)則見下面) // 若該entry的value為expunged,說明已不在dirty中(因?yàn)閑.p置為expunged只有一處,即dirty時新建時調(diào)用的tryExpungeLocked函數(shù),那里并未復(fù)制到dirty中), // 在mu鎖解鎖之前,必須要重新加入到dirty中,這里置為nil,返回true // 若該entry的value不為expunged,則返回false func (e *entry) unexpungeLocked() (wasExpunged bool) {return atomic.CompareAndSwapPointer(&e.p, expunged, nil) }// 新建dirty(此時mu鎖是鎖定的) func (m *Map) dirtyLocked() {if m.dirty != nil {return}// 獲取readread, _ := m.read.Load().(readOnly)// 新建dirtym.dirty = make(map[interface{}]*entry, len(read.m))// 遍歷read,逐個值拷貝給dirty// 不能直接整個map賦值,是因?yàn)閞ead中可能有些entry已經(jīng)被標(biāo)記為刪除(為nil),我們只需復(fù)制那些未被刪除的// 注意:若entry為nil,則置為expunged,標(biāo)記為徹底刪除,不再復(fù)制到dirty中for k, e := range read.m {if !e.tryExpungeLocked() {m.dirty[k] = e}} }// 嘗試刪除本entry(此時mu鎖是鎖定的) // 返回值表本entry是否已刪除 func (e *entry) tryExpungeLocked() (isExpunged bool) {// 原子地重新讀取p := atomic.LoadPointer(&e.p)// 指針為nil時(此時mu是鎖定的,為什么還要for循環(huán)來嘗試CAS呢,不懂?????)for p == nil {// 用CAS操作,置為expunged,以此來標(biāo)記本條目已被徹底刪除,成功則返回if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {return true}// CAS失敗后再獲取存儲的指針,若為nil,則一直嘗試;若不為nil了,則跳出p = atomic.LoadPointer(&e.p)}// 若為expunged說明,已被標(biāo)記刪除,否則,未刪除return p == expunged }// LoadOrStore 若key存在,則返回對應(yīng)的value,loaded為true // 若key不存在,則存儲給定的value值;loaded為false func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {// 先嘗試從read中讀取,若read中存在,則嘗試loadorstore,成功則返回read, _ := m.read.Load().(readOnly)if e, ok := read.m[key]; ok {actual, loaded, ok := e.tryLoadOrStore(value)if ok {return actual, loaded}}// read中不存在該key,或存在但tryLoadOrStore失敗,則加鎖m.mu.Lock()// 雙重檢測(因?yàn)榧渔i過程中,dirty可能被提升為read,dirty被置為nil,此時read中可能存在該key了)read, _ = m.read.Load().(readOnly)// read中已存在該keyif e, ok := read.m[key]; ok {// 若為expunged狀態(tài),說明dirty中已被刪除,需添加到dirty中if e.unexpungeLocked() {m.dirty[key] = e}actual, loaded, _ = e.tryLoadOrStore(value) // 更新后read,dirty中都是最新的} else if e, ok := m.dirty[key]; ok { // dirty中存在該key// 在dirty中嘗試loadorstoreactual, loaded, _ = e.tryLoadOrStore(value)// 未命中時的處理m.missLocked()} else { // dirty中也不存在該key// 若此時dirty中不包含read中不存在的key,(即dirty中的key在read中都存在),說明是第一次新加key到dirty中if !read.amended {// 建立dirtym.dirtyLocked()// amended 置為true,因?yàn)榇舜翁砑觡ey后,dirty中就包含了read中不存在的keym.read.Store(readOnly{m: read.m, amended: true})}// dirty中新建該keym.dirty[key] = newEntry(value)actual, loaded = value, false}m.mu.Unlock()return actual, loaded }

    注意事項(xiàng):

    1. Store()時,若read中有,且不為expunged,則優(yōu)先在read中修改

    2. 若read中不存在,dirty中存在,則修改dirty中的;若dirty中不存在,可能需要新建/重建dirty,此時amended為true

    3. Store() 可以多協(xié)程環(huán)境下調(diào)用,需多注意并發(fā)問題,比如判斷read是否有key的雙重檢測

    3.2 讀取(Load(),LoadOrStore()),?源碼及分析

    // 讀取key的值 func (m *Map) Load(key interface{}) (value interface{}, ok bool) {read, _ := m.read.Load().(readOnly)// 先嘗試從read中讀取e, ok := read.m[key]// read中不存在,且dirty中包含read中不存在的keyif !ok && read.amended {// 加鎖m.mu.Lock()// 雙重檢測(因?yàn)榧渔i過程中,可能dirty被提升為read,dirty被置為nil,此時read中可能有該key了)read, _ = m.read.Load().(readOnly)e, ok = read.m[key]// read中仍然不存在該key,且dirty中包含read中不存在的keyif !ok && read.amended {e, ok = m.dirty[key]// 無論dirty中是否存在該key,都要記錄一次未命中,然后根據(jù)情況,決定是否把dirty提升為readm.missLocked()}m.mu.Unlock()}// 仍然不存在,返回if !ok {return nil, false}// 通過entry加載數(shù)據(jù)return e.load() }// 從entry中加載值 // 值為nil,expunged時,說明已經(jīng)被刪除 func (e *entry) load() (value interface{}, ok bool) {// 原子地重新讀取值p := atomic.LoadPointer(&e.p)if p == nil || p == expunged {return nil, false}// 先轉(zhuǎn)換為接口指針,再取值return *(*interface{})(p), true }// 未命中時的處理(此時mu鎖是鎖定的) func (m *Map) missLocked() {m.misses++// 未命中次數(shù)沒有達(dá)到dirty時,返回,即平均每個key達(dá)到未命中一次if m.misses < len(m.dirty) {return}// 把dirty提升為read,直接整個map賦值,里面的amended未賦值,即默認(rèn)false,表dirty中不包含read中不存在的key,因?yàn)橄旅鎑irty就要被置為nil了m.read.Store(readOnly{m: m.dirty})m.dirty = nilm.misses = 0 }// LoadOrStore 若key存在,則返回對應(yīng)的value,loaded為true // 若key不存在,則存儲給定的value值;loaded為false func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {// 先嘗試從read中讀取,若read中存在,則嘗試loadorstore,成功則返回read, _ := m.read.Load().(readOnly)if e, ok := read.m[key]; ok {actual, loaded, ok := e.tryLoadOrStore(value)if ok {return actual, loaded}}// read中不存在該key,或存在但tryLoadOrStore失敗,則加鎖m.mu.Lock()// 雙重檢測(因?yàn)榧渔i過程中,dirty可能被提升為read,dirty被置為nil,此時read中可能存在該key了)read, _ = m.read.Load().(readOnly)// read中已存在該keyif e, ok := read.m[key]; ok {// 若為expunged狀態(tài),說明dirty中已被刪除,需添加到dirty中if e.unexpungeLocked() {m.dirty[key] = e}actual, loaded, _ = e.tryLoadOrStore(value) // 更新后read,dirty中都是最新的} else if e, ok := m.dirty[key]; ok { // dirty中存在該key// 在dirty中嘗試loadorstoreactual, loaded, _ = e.tryLoadOrStore(value)// 未命中時的處理m.missLocked()} else { // dirty中也不存在該key// 若此時dirty中不包含read中不存在的key,(即dirty中的key在read中都存在),說明是第一次新加key到dirty中if !read.amended {// 建立dirtym.dirtyLocked()// amended 置為true,因?yàn)榇舜翁砑觡ey后,dirty中就包含了read中不存在的keym.read.Store(readOnly{m: read.m, amended: true})}// dirty中新建該keym.dirty[key] = newEntry(value)actual, loaded = value, false}m.mu.Unlock()return actual, loaded }// tryLoadOrStore(多協(xié)程操作,此時mu鎖未鎖定) // 若該條目未被刪除,原子地讀取value值,loaded=true,ok=true // 若該條目已被刪除(expunged或nil),則tryLoadOrStore函數(shù)不修改,直接返回,loaded==false,ok==false func (e *entry) tryLoadOrStore(i interface{}) (actual interface{}, loaded, ok bool) {p := atomic.LoadPointer(&e.p)// 已被刪除,dirty中不存在該entry,不可賦值if p == expunged {return nil, false, false}// 正常有效值,則返回其值if p != nil {return *(*interface{})(p), true, true}// 下面是為nil值的情況// Copy the interface after the first load to make this method more amenable// to escape analysis: if we hit the "load" path or the entry is expunged, we// shouldn't bother heap-allocating.ic := ifor {// CAS操作,賦值if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {return i, false, true}p = atomic.LoadPointer(&e.p)// 已被刪除,說明其他協(xié)程剛剛刪除了,返回失敗if p == expunged {return nil, false, false}// 正常值,返回成功if p != nil {return *(*interface{})(p), true, true}} }

    注意事項(xiàng):

    1. Load()時,若read中存在,直接讀取值,返回

    2. 若read中不存在,則未命中次數(shù)+1,累計次數(shù)達(dá)到dirty長度時,也就是平均每個key達(dá)到一次未命中,則把dirty提升為read

    3. Load(),LoadOrStore() 可以多協(xié)程環(huán)境下調(diào)用,需多注意并發(fā)問題,比如判斷read是否有key的雙重檢測

    3.3?刪除(Delete()),?源碼及分析

    // 刪除一個key func (m *Map) Delete(key interface{}) {// 優(yōu)先從read中讀取read, _ := m.read.Load().(readOnly)e, ok := read.m[key]// read中不存在,但dirty中包含read中不存在的key時,需要從dirty中刪除if !ok && read.amended {// 必加鎖m.mu.Lock()// 雙重檢測(因?yàn)榧渔i的過程中dirty可能被提升為read,dirty被置為nil,此時read中可能有該key了)read, _ = m.read.Load().(readOnly)e, ok = read.m[key]// 仍然不存在,但dirty中包含read中不存在的key時,從dirty中刪除(在鎖定中,所以可以真正刪除)if !ok && read.amended {delete(m.dirty, key)}m.mu.Unlock()}// 存在時,一定是存在于read中,只需通過entry標(biāo)記為已刪除(置為nil)if ok {e.delete()} }// 標(biāo)記一個條目刪除,置為nil(mu鎖未加鎖,需考慮多協(xié)程安全,本entry此時存在于read中) func (e *entry) delete() (hadValue bool) {for {// 原子地重新讀取p := atomic.LoadPointer(&e.p)// nil 表已被其他協(xié)程通過Delete()標(biāo)記刪除,不再操作// expunged 表已被其他協(xié)程提升dirty為read時,標(biāo)記刪除,不再操作if p == nil || p == expunged {return false}// CAS操作,置為nilif atomic.CompareAndSwapPointer(&e.p, p, nil) {return true}} }

    注意事項(xiàng):

    1. Delete()時,若read中存在,直接在read中標(biāo)記為刪除(置nil),并非真正的立即刪掉,這樣可以下次賦值時直接操作,達(dá)到重復(fù)使用的目的

    2. 若read中存在key,標(biāo)記刪除后,此時dirty里面的該key是不準(zhǔn)確的,這種不準(zhǔn)確是臨時的,因?yàn)槊看瓮鵧irty中新加入key的時候,會先判斷dirty是否為nil,為空則新建,且從read中篩選一遍,這樣dirty又是準(zhǔn)確的了;以后dirty被提升為read時,為nil的會被置為expunged,來標(biāo)記為徹底刪除,不會壓入到dirty中

    3. 若dirty中存在key,則徹底刪除掉

    4. Delete()可以多協(xié)程環(huán)境下調(diào)用,需多注意并發(fā)問題,比如判斷read是否有key的雙重檢測,比如置為nil時的for循環(huán)CAS操作

    5. 切記:dirty為nil 和 read.amended 為false,是同時存在的

    ? ? 要么dirty = nil,read.amended = false,要么dirty != nil,amended = true

    總結(jié)

    以上是生活随笔為你收集整理的go sync.map 源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。