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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

go语言api源码中文版_Go语言学习——sync.map源码剖析

發布時間:2023/12/10 编程问答 72 豆豆
生活随笔 收集整理的這篇文章主要介紹了 go语言api源码中文版_Go语言学习——sync.map源码剖析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1.簡介
最近看了下Sync包,詳讀了sync.map源碼,感覺源碼實現還是比較巧妙的,有不少可以學習的地方;在講源碼前,先看下sync.map的"歷史",從網上搜資料,sync.map是Go語言在1.9版本才引入的并發安全的map,對此,有些同學心中可能會有個疑問,如果是支持并發,為什么不采取鎖map的方式,為啥還要在單獨搞個sync.map結構呢?我們先看下鎖map存在的問題:

參考:go語言中文文檔:www.topgoer.com

轉自:https://studygolang.com/topics/12363#reply0
1)mutex + map
最簡單的方案就是在map上加個鎖,針對map的所有操作都要提前加鎖,其存在問題也很明顯,鎖競爭會非常頻繁;
2)rwmutex + map
優化一點,依據場景,如果是讀操作多于寫操作,可以把mutex換成rwmutex,相比方案一,有一定優化、至少讀讀之間不會存在互斥,不過,讀寫之間還會存在阻塞;
根據鎖map的優化迭代方案可知,在讀讀場景下,rwmutex + map可以并發、不存在阻塞,但是,讀寫還是存在阻塞,而sync.map要做的事情就是能進一步優化:對于map的各種操作,盡可能不阻塞;為此,sync.map采用了兩級緩存實現,一級緩存做無鎖并發,二級緩存做有鎖并發,如:


對上圖說明兩點:
1)針對sync.map的各種操作,都先經過一級緩存,一級緩存采用無鎖的方式,只要不出現擊穿,即key都在一級緩存中可以找到,則就不會訪問到二級緩存;
2)一級緩存和二級緩存之間存在數據同步,二級緩存數據相對更全一些,所以當一級緩存數據比較久時,可以將二級緩存數據同步一下,該情況是在讀擊穿時處理;在不擊穿的前提下,一級緩存中可能有數據刪除,數據移除情況也要同步給二級緩存,清除廢棄數據、減少空間占用,該情況是在寫擊穿并且是一、二級緩存都不存在鍵的情況處理,總之,同步的原則是:一級緩存數據盡可能新;一級緩存數據只能是二級緩存的子集;

2.實現
sync.map的優勢是理想情況下以無鎖代替有鎖、提高性能,但存在擊穿后不得不加鎖的問題,一旦擊穿進入二級緩存,就要進行鎖操作了,所以sync.map不太適用于寫多讀少以及頻繁創建新鍵的情況;因為要考慮擊穿問題,所以sync.map的實現也是圍繞擊穿考慮的。 2.1讀操作
讀操作比較簡單,步驟是:
1)查看一級緩存中是否有key,有就返回對應value;
2)如果沒有則進入讀擊穿,加鎖后,在復看一級緩存中是否有key(復看是因為存在二級緩存向一級緩存同步數據的情況),有就返回對應value;
3)如果沒有則看二級緩存中有沒有,有就返回對應value,此時出現讀擊穿,會進入讀擊穿保護機制——擊穿達到一定次數,會將二級緩存數據同步到一級緩存;
需要注意的是,在返回value時要檢測value的有效性,如果已經廢棄(expunged狀態),則不用返回。
2.2寫操作
寫操作相對復雜,根據key是否存在的情況,可以分為create和update,步驟是:
1)查看一級緩存中是否有key,有就嘗試更新,之所以是嘗試是因為還要檢查數據是否已經廢棄,如果已經廢棄,即使key在一級緩存中存在,也是擊穿效果,因為二級緩存中沒有;
2)如果一級緩存操作失敗,加鎖后,在復看一級緩存,如果有key,則更新value,并檢測value是否為廢棄狀態,如果是,則將key、value寫入二級緩存;
3)如果一級緩存中一直沒有key,但二級緩存中有,則直接更新數據;
4)如果一級緩存和二級緩存都沒有key,則將key、value寫入二級緩存,此時會嘗試將一級緩存數據同步給二級緩存,用于刪除廢棄數據(將一級緩存中的刪除數據設置為expunged狀態),因為只有該情況下,一級緩存數據可能是二級緩存數據的子集,所以當插入全新的key時,才會嘗試更新緩存數據、移除廢棄數據;
2.3刪除操作
刪除采取的是延遲刪除操作,對于待刪除數據,其value先設置為nil,優先從一級緩存刪除,如果一級緩存沒有,再去二級緩存中刪除。
2.4源碼
以1.14.4版本為例,處理源碼是:

func (m *Map) Load(key interface{}) (value interface{}, ok bool) { read, _ := m.read.Load().(readOnly) e, ok := read.m[key] // 一級緩存沒有查到key,加鎖、復查,amended用于判斷一級緩存和二級緩存是否一致 if !ok && read.amended { m.mu.Lock() read, _ = m.read.Load().(readOnly) e, ok = read.m[key] // 一級緩存還是沒有查到key,則擊穿進入二級緩存 if !ok && read.amended { e, ok = m.dirty[key] m.missLocked() // 讀擊穿保護,根據擊穿次數決定是否要同步數據 } m.mu.Unlock() } if !ok { return nil, false } return e.load()}func (m *Map) Store(key, value interface{}) { read, _ := 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 { // 一級緩存中有key,則更新value,同時,還要查看value是否已經廢棄,如果廢棄還要將數據寫入二級緩存,確保下次同步前,二級緩存數據的完整性,因為操作到二級緩存,所以需要放在鎖操作下;這也是為什么tryStore只是嘗試存儲 if e.unexpungeLocked() { m.dirty[key] = e } e.storeLocked(&value) } else if e, ok := m.dirty[key]; ok { e.storeLocked(&value) } else { // 對于key完全不存在的情況,嘗試數據同步,從一級緩存到二級緩存 if !read.amended { m.dirtyLocked() // 數據同步時,廢棄數據不會同步,廢棄數據會設置為expunged狀態 m.read.Store(readOnly{m: read.m, amended: true}) } m.dirty[key] = newEntry(value) } m.mu.Unlock()}// Delete部分相對簡單,主要是將value設置為nilfunc (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 } if atomic.CompareAndSwapPointer(&e.p, p, nil) { return true } }}

3.總結
sync.map是以無鎖操作一級緩存的方式支持并發、提高性能,而根據其實現可知,sync.map適用于讀多、更新多、新建少的場景(新建情況下,可能會帶來較大的開銷,比如:讀擊穿、數據剛從二級緩存同步到一級緩存后,又要新建key,數據又要反向同步一次)。

總結

以上是生活随笔為你收集整理的go语言api源码中文版_Go语言学习——sync.map源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。