日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Golang中sync.Map的坑

這篇文章主要介紹了Golang中sync.Map的坑,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊、虛擬空間、營銷軟件、網(wǎng)站建設(shè)、開福網(wǎng)站維護(hù)、網(wǎng)站推廣。

緣起

Go 1.15 發(fā)布了,我也第一時(shí)間更新了這個版本,畢竟對 Go 的穩(wěn)定性還是有一些信心的,于是直接在公司上了生產(chǎn)。

結(jié)果,上線幾分鐘,就出現(xiàn)了 OOM,于是 pprof 了一下 heap,然后趕緊回滾,發(fā)現(xiàn)某塊本應(yīng)該在一次請求結(jié)束時(shí)被釋放的內(nèi)存,被保留了下來而且一直在增長,如圖(圖中的 linkBufferNode):

Golang中sync.Map的坑

這次上線的變更只有 Go 版本的升級,沒有任何其它變動,于是在本地開始測試,發(fā)現(xiàn)在本地也能百分百復(fù)現(xiàn)。

排查過程

看了 Go 1.15 的 Release Note,發(fā)現(xiàn)有倆高度疑似的東西:

  1. 去除了一些 GC Data,使得 binary size 減少了 5%;

  2. 新的內(nèi)存分配算法。

于是改 runtime,關(guān)閉新的內(nèi)存分配算法,切換回舊的,等等一頓操作猛如虎下來,發(fā)現(xiàn)問題還是沒解決,現(xiàn)象仍然存在。

Golang中sync.Map的坑

于是實(shí)在不行,祭出了GODEBUG="allocfreetrace=1大法,肉眼從 100MB+ 的日志文件里面看啊看啊看啊看啊看啊看啊看啊看啊看啊看啊……(此處省略心酸過程)

最終直覺告訴我,這個問題可能和 Go 1.15 中 sync.Map 的改動有關(guān)(別問我為啥,真的是直覺,我也說不出來)。

示例代碼

為了方便講解,我寫了一個最小可復(fù)現(xiàn)的代碼,如下:

package main

import (
   "sync"
)

var sm sync.Map

func insertKeys() {
   keys := make([]interface{}, 0, 10)
   // Store some keys
   for i := 0; i < 10; i++ {
       v := make([]int, 1000)
       keys = append(keys, &v)
       sm.Store(keys[i], struct{}{})
   }
   // delete some keys, but not all keys
   for i, k := range keys {
       if i%2 == 0 {
           continue
       }
       sm.Delete(k)
   }
}

func shutdown() {
   sm.Range(func(key, value interface{}) bool {
       // do something to key
       return true
   })
}

func main() {
   insertKeys()
   // do something ...
   shutdown()
}

Go 1.15 中 sync.Map 改動

在 Go 1.15 中,sync.Map 增加了一個方法LoadAndDelete,具體的 issue 在這:https://github.com/golang/go/issues/33762CL, 在這:https://go-review.googlesource.com/c/go/+/205899/。

為什么我確認(rèn)是這個改動導(dǎo)致的呢?很簡單:我在本地把這個改動 revert 掉了,問題就沒了,好了關(guān)機(jī)下班……

當(dāng)然沒這么簡單,知其然要知其所以然,于是開始看到底改了哪塊……(此處省略 100000 字)

最終發(fā)現(xiàn),關(guān)鍵代碼是這段:

// LoadAndDelete deletes the value for a key, returning the previous value if any.
// The loaded result reports whether the key was present.
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
   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 {
           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 e.delete()
   }
   return nil, false
}

// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
   m.LoadAndDelete(key)
}

func (e *entry) delete() (value interface{}, ok bool) {
   for {
       p := atomic.LoadPointer(&e.p)
       if p == nil || p == expunged {
           return nil, false
       }
       if atomic.CompareAndSwapPointer(&e.p, p, nil) {
           return *(*interface{})(p), true
       }
   }
}

在這段代碼中,會發(fā)現(xiàn)在 Delete 的時(shí)候,并沒有真正刪除掉 key,而是從 key 中取出了 entry,然后把 entry 設(shè)為 nil……

所以,在我們場景中,我們把一個連接作為 key 放了進(jìn)去,于是和這個連接相關(guān)的比如 buffer 的內(nèi)存就永遠(yuǎn)無法釋放了……

那么為什么在 Go 1.14 中沒有問題呢?以下是 Go 1.14 的代碼:

// 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()
   }
}

在 Go 1.14 中,如果 key 在 dirty 中,是會被刪除的;而湊巧,我們其實(shí) “誤用” 了 sync.Map,在我們的使用過程中沒有讀操作,導(dǎo)致所有的 key 其實(shí)都在 dirty 里面,所以當(dāng)調(diào)用 Delete 的時(shí)候是會被真正刪除的。

要注意,無論哪個版本的 Go,一旦 key 升級到了 read 中,都是永遠(yuǎn)不會被刪除的。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Golang中sync.Map的坑”這篇文章對大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!


當(dāng)前題目:Golang中sync.Map的坑
當(dāng)前鏈接:http://www.dlmjj.cn/article/gehoos.html