go语言api源码中文版_Go语言学习——sync.map源码剖析
1.簡(jiǎn)介
最近看了下Sync包,詳讀了sync.map源碼,感覺(jué)源碼實(shí)現(xiàn)還是比較巧妙的,有不少可以學(xué)習(xí)的地方;在講源碼前,先看下sync.map的"歷史",從網(wǎng)上搜資料,sync.map是Go語(yǔ)言在1.9版本才引入的并發(fā)安全的map,對(duì)此,有些同學(xué)心中可能會(huì)有個(gè)疑問(wèn),如果是支持并發(fā),為什么不采取鎖map的方式,為啥還要在單獨(dú)搞個(gè)sync.map結(jié)構(gòu)呢?我們先看下鎖map存在的問(wèn)題:
參考:go語(yǔ)言中文文檔:www.topgoer.com
轉(zhuǎn)自:https://studygolang.com/topics/12363#reply0
1)mutex + map
最簡(jiǎn)單的方案就是在map上加個(gè)鎖,針對(duì)map的所有操作都要提前加鎖,其存在問(wèn)題也很明顯,鎖競(jìng)爭(zhēng)會(huì)非常頻繁;
2)rwmutex + map
優(yōu)化一點(diǎn),依據(jù)場(chǎng)景,如果是讀操作多于寫操作,可以把mutex換成rwmutex,相比方案一,有一定優(yōu)化、至少讀讀之間不會(huì)存在互斥,不過(guò),讀寫之間還會(huì)存在阻塞;
根據(jù)鎖map的優(yōu)化迭代方案可知,在讀讀場(chǎng)景下,rwmutex + map可以并發(fā)、不存在阻塞,但是,讀寫還是存在阻塞,而sync.map要做的事情就是能進(jìn)一步優(yōu)化:對(duì)于map的各種操作,盡可能不阻塞;為此,sync.map采用了兩級(jí)緩存實(shí)現(xiàn),一級(jí)緩存做無(wú)鎖并發(fā),二級(jí)緩存做有鎖并發(fā),如:
對(duì)上圖說(shuō)明兩點(diǎn):
1)針對(duì)sync.map的各種操作,都先經(jīng)過(guò)一級(jí)緩存,一級(jí)緩存采用無(wú)鎖的方式,只要不出現(xiàn)擊穿,即key都在一級(jí)緩存中可以找到,則就不會(huì)訪問(wèn)到二級(jí)緩存;
2)一級(jí)緩存和二級(jí)緩存之間存在數(shù)據(jù)同步,二級(jí)緩存數(shù)據(jù)相對(duì)更全一些,所以當(dāng)一級(jí)緩存數(shù)據(jù)比較久時(shí),可以將二級(jí)緩存數(shù)據(jù)同步一下,該情況是在讀擊穿時(shí)處理;在不擊穿的前提下,一級(jí)緩存中可能有數(shù)據(jù)刪除,數(shù)據(jù)移除情況也要同步給二級(jí)緩存,清除廢棄數(shù)據(jù)、減少空間占用,該情況是在寫擊穿并且是一、二級(jí)緩存都不存在鍵的情況處理,總之,同步的原則是:一級(jí)緩存數(shù)據(jù)盡可能新;一級(jí)緩存數(shù)據(jù)只能是二級(jí)緩存的子集;
2.實(shí)現(xiàn)
sync.map的優(yōu)勢(shì)是理想情況下以無(wú)鎖代替有鎖、提高性能,但存在擊穿后不得不加鎖的問(wèn)題,一旦擊穿進(jìn)入二級(jí)緩存,就要進(jìn)行鎖操作了,所以sync.map不太適用于寫多讀少以及頻繁創(chuàng)建新鍵的情況;因?yàn)橐紤]擊穿問(wèn)題,所以sync.map的實(shí)現(xiàn)也是圍繞擊穿考慮的。 2.1讀操作
讀操作比較簡(jiǎn)單,步驟是:
1)查看一級(jí)緩存中是否有key,有就返回對(duì)應(yīng)value;
2)如果沒(méi)有則進(jìn)入讀擊穿,加鎖后,在復(fù)看一級(jí)緩存中是否有key(復(fù)看是因?yàn)榇嬖诙?jí)緩存向一級(jí)緩存同步數(shù)據(jù)的情況),有就返回對(duì)應(yīng)value;
3)如果沒(méi)有則看二級(jí)緩存中有沒(méi)有,有就返回對(duì)應(yīng)value,此時(shí)出現(xiàn)讀擊穿,會(huì)進(jìn)入讀擊穿保護(hù)機(jī)制——擊穿達(dá)到一定次數(shù),會(huì)將二級(jí)緩存數(shù)據(jù)同步到一級(jí)緩存;
需要注意的是,在返回value時(shí)要檢測(cè)value的有效性,如果已經(jīng)廢棄(expunged狀態(tài)),則不用返回。
2.2寫操作
寫操作相對(duì)復(fù)雜,根據(jù)key是否存在的情況,可以分為create和update,步驟是:
1)查看一級(jí)緩存中是否有key,有就嘗試更新,之所以是嘗試是因?yàn)檫€要檢查數(shù)據(jù)是否已經(jīng)廢棄,如果已經(jīng)廢棄,即使key在一級(jí)緩存中存在,也是擊穿效果,因?yàn)槎?jí)緩存中沒(méi)有;
2)如果一級(jí)緩存操作失敗,加鎖后,在復(fù)看一級(jí)緩存,如果有key,則更新value,并檢測(cè)value是否為廢棄狀態(tài),如果是,則將key、value寫入二級(jí)緩存;
3)如果一級(jí)緩存中一直沒(méi)有key,但二級(jí)緩存中有,則直接更新數(shù)據(jù);
4)如果一級(jí)緩存和二級(jí)緩存都沒(méi)有key,則將key、value寫入二級(jí)緩存,此時(shí)會(huì)嘗試將一級(jí)緩存數(shù)據(jù)同步給二級(jí)緩存,用于刪除廢棄數(shù)據(jù)(將一級(jí)緩存中的刪除數(shù)據(jù)設(shè)置為expunged狀態(tài)),因?yàn)橹挥性撉闆r下,一級(jí)緩存數(shù)據(jù)可能是二級(jí)緩存數(shù)據(jù)的子集,所以當(dāng)插入全新的key時(shí),才會(huì)嘗試更新緩存數(shù)據(jù)、移除廢棄數(shù)據(jù);
2.3刪除操作
刪除采取的是延遲刪除操作,對(duì)于待刪除數(shù)據(jù),其value先設(shè)置為nil,優(yōu)先從一級(jí)緩存刪除,如果一級(jí)緩存沒(méi)有,再去二級(jí)緩存中刪除。
2.4源碼
以1.14.4版本為例,處理源碼是:
3.總結(jié)
sync.map是以無(wú)鎖操作一級(jí)緩存的方式支持并發(fā)、提高性能,而根據(jù)其實(shí)現(xiàn)可知,sync.map適用于讀多、更新多、新建少的場(chǎng)景(新建情況下,可能會(huì)帶來(lái)較大的開(kāi)銷,比如:讀擊穿、數(shù)據(jù)剛從二級(jí)緩存同步到一級(jí)緩存后,又要新建key,數(shù)據(jù)又要反向同步一次)。
總結(jié)
以上是生活随笔為你收集整理的go语言api源码中文版_Go语言学习——sync.map源码剖析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数字格式化
- 下一篇: 怎么成为日上会员直邮_18个日上直邮问题