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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

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

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

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

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

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


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

2.實(shí)現(xiàn)
sync.map的優(yōu)勢是理想情況下以無鎖代替有鎖、提高性能,但存在擊穿后不得不加鎖的問題,一旦擊穿進(jìn)入二級緩存,就要進(jìn)行鎖操作了,所以sync.map不太適用于寫多讀少以及頻繁創(chuàng)建新鍵的情況;因?yàn)橐紤]擊穿問題,所以sync.map的實(shí)現(xiàn)也是圍繞擊穿考慮的。 2.1讀操作
讀操作比較簡單,步驟是:
1)查看一級緩存中是否有key,有就返回對應(yīng)value;
2)如果沒有則進(jìn)入讀擊穿,加鎖后,在復(fù)看一級緩存中是否有key(復(fù)看是因?yàn)榇嬖诙壘彺嫦蛞患壘彺嫱綌?shù)據(jù)的情況),有就返回對應(yīng)value;
3)如果沒有則看二級緩存中有沒有,有就返回對應(yīng)value,此時(shí)出現(xiàn)讀擊穿,會進(jìn)入讀擊穿保護(hù)機(jī)制——擊穿達(dá)到一定次數(shù),會將二級緩存數(shù)據(jù)同步到一級緩存;
需要注意的是,在返回value時(shí)要檢測value的有效性,如果已經(jīng)廢棄(expunged狀態(tài)),則不用返回。
2.2寫操作
寫操作相對復(fù)雜,根據(jù)key是否存在的情況,可以分為create和update,步驟是:
1)查看一級緩存中是否有key,有就嘗試更新,之所以是嘗試是因?yàn)檫€要檢查數(shù)據(jù)是否已經(jīng)廢棄,如果已經(jīng)廢棄,即使key在一級緩存中存在,也是擊穿效果,因?yàn)槎壘彺嬷袥]有;
2)如果一級緩存操作失敗,加鎖后,在復(fù)看一級緩存,如果有key,則更新value,并檢測value是否為廢棄狀態(tài),如果是,則將key、value寫入二級緩存;
3)如果一級緩存中一直沒有key,但二級緩存中有,則直接更新數(shù)據(jù);
4)如果一級緩存和二級緩存都沒有key,則將key、value寫入二級緩存,此時(shí)會嘗試將一級緩存數(shù)據(jù)同步給二級緩存,用于刪除廢棄數(shù)據(jù)(將一級緩存中的刪除數(shù)據(jù)設(shè)置為expunged狀態(tài)),因?yàn)橹挥性撉闆r下,一級緩存數(shù)據(jù)可能是二級緩存數(shù)據(jù)的子集,所以當(dāng)插入全新的key時(shí),才會嘗試更新緩存數(shù)據(jù)、移除廢棄數(shù)據(jù);
2.3刪除操作
刪除采取的是延遲刪除操作,對于待刪除數(shù)據(jù),其value先設(shè)置為nil,優(yōu)先從一級緩存刪除,如果一級緩存沒有,再去二級緩存中刪除。
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,加鎖、復(fù)查,amended用于判斷一級緩存和二級緩存是否一致 if !ok && read.amended { m.mu.Lock() read, _ = m.read.Load().(readOnly) e, ok = read.m[key] // 一級緩存還是沒有查到key,則擊穿進(jìn)入二級緩存 if !ok && read.amended { e, ok = m.dirty[key] m.missLocked() // 讀擊穿保護(hù),根據(jù)擊穿次數(shù)決定是否要同步數(shù)據(jù) } 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,同時(shí),還要查看value是否已經(jīng)廢棄,如果廢棄還要將數(shù)據(jù)寫入二級緩存,確保下次同步前,二級緩存數(shù)據(jù)的完整性,因?yàn)椴僮鞯蕉壘彺?#xff0c;所以需要放在鎖操作下;這也是為什么tryStore只是嘗試存儲 if e.unexpungeLocked() { m.dirty[key] = e } e.storeLocked(&value) } else if e, ok := m.dirty[key]; ok { e.storeLocked(&value) } else { // 對于key完全不存在的情況,嘗試數(shù)據(jù)同步,從一級緩存到二級緩存 if !read.amended { m.dirtyLocked() // 數(shù)據(jù)同步時(shí),廢棄數(shù)據(jù)不會同步,廢棄數(shù)據(jù)會設(shè)置為expunged狀態(tài) m.read.Store(readOnly{m: read.m, amended: true}) } m.dirty[key] = newEntry(value) } m.mu.Unlock()}// Delete部分相對簡單,主要是將value設(shè)置為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.總結(jié)
sync.map是以無鎖操作一級緩存的方式支持并發(fā)、提高性能,而根據(jù)其實(shí)現(xiàn)可知,sync.map適用于讀多、更新多、新建少的場景(新建情況下,可能會帶來較大的開銷,比如:讀擊穿、數(shù)據(jù)剛從二級緩存同步到一級緩存后,又要新建key,數(shù)據(jù)又要反向同步一次)。

總結(jié)

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

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