sync.Map是一個(gè)并發(fā)安全的map,它是通過(guò)雙層的數(shù)據(jù)來(lái)存儲(chǔ)的胸囱,第一層read饰豺,可以實(shí)現(xiàn)無(wú)鎖的讀取凤跑,因此sync.Map適合用于讀多寫(xiě)少的場(chǎng)景,結(jié)構(gòu)如下
個(gè)人理解sync.Map的結(jié)構(gòu)有點(diǎn)像常用的緩存設(shè)計(jì)凰慈,read就是緩存層森篷,它可以進(jìn)行無(wú)鎖的讀取;dirty就是數(shù)據(jù)庫(kù)層碉咆,它存儲(chǔ)了所有的數(shù)據(jù),需要加鎖進(jìn)行操作顽馋。dirty在一定情況下會(huì)提升到read熊痴,同時(shí)dirty數(shù)據(jù)會(huì)從read中進(jìn)行拷貝
dirty和read的轉(zhuǎn)換
read可以理解為緩存,可以無(wú)鎖進(jìn)行操作混聊,所以很快旷太,dirty即為全量數(shù)據(jù)
- dirty提升為read
Map中有一個(gè)變量為misses,它記錄了讀取read沒(méi)命中的次數(shù),當(dāng)misses次數(shù)超過(guò)了dirty中數(shù)據(jù)個(gè)數(shù)的時(shí)候供璧,就會(huì)將dirty提升為read存崖,同時(shí)dirty置為nil。每次讀取read失敗的時(shí)候都會(huì)進(jìn)行判斷是否需要將dirty提升到read睡毒,升級(jí)結(jié)果如下圖所示来惧。其實(shí)就是將dirty賦值到read.m
- 從read復(fù)制數(shù)據(jù)到dirty
在寫(xiě)入數(shù)據(jù)的時(shí)候,如果read未命中演顾,會(huì)判斷dirty是否為nil供搀,如果是,則會(huì)從read中復(fù)制數(shù)據(jù)到dirty中钠至。注意這里expunged就會(huì)產(chǎn)生作用了葛虐,在復(fù)制的過(guò)程中,如果read中某個(gè)entry存儲(chǔ)的value的數(shù)據(jù)為nil棉钧,說(shuō)明這個(gè)數(shù)據(jù)被刪除了屿脐,次數(shù)dirty不會(huì)復(fù)制這個(gè)entry,同時(shí)會(huì)將這個(gè)entry.p置為expunged宪卿。如下圖所示摄悯。至于key4和key6為什么為nil,可以看刪除操作
樣例圖
讀取操作
由此可以發(fā)現(xiàn)愧捕,sync.Map的讀取很簡(jiǎn)單奢驯,首先無(wú)鎖讀取read,如果沒(méi)有再去dirty中讀取次绘,其中包括了double check瘪阁,即加鎖后再check一邊read的數(shù)據(jù)
寫(xiě)入操作
對(duì)于寫(xiě)入,需要保證read和dirty數(shù)據(jù)一致性邮偎,這里有兩種情況管跺,一種是read中有相應(yīng)的key,一種是read中沒(méi)有相應(yīng)key
如果read中沒(méi)有相應(yīng)的鍵,只需要在dirty中寫(xiě)入相應(yīng)的key/value就行了歪赢,當(dāng)然這里需要加鎖進(jìn)行寫(xiě)入即可系枪。例如,如果我們此時(shí)需要插入key5艇拍,此時(shí)read中并沒(méi)有key5,則加鎖后往dirty中設(shè)置key5即可宠纯,同樣的卸夕,key3也是這種情況
如果read中有相應(yīng)的鍵,這里需要對(duì)key對(duì)應(yīng)的entry中保存的指針進(jìn)行判斷:
- 如果entry.p不為expunged婆瓜,可以將其直接指向value即可快集,因?yàn)閐irty和read.m的結(jié)構(gòu)都是map[interface{}]*entry贡羔,它們都是指針結(jié)構(gòu),因此只需要替換掉entry中的指針个初,就可以實(shí)現(xiàn)dirty和read同時(shí)更新乖寒。對(duì)應(yīng)上圖中key1和key4情況
- 如果entry.p為expunged,那說(shuō)明這個(gè)key在read中存在院溺,但是在dirty中不存在宵统。這種情況就不能直接更新entry.p了,這樣會(huì)導(dǎo)致數(shù)據(jù)不同步覆获。這種情況下马澈,就需要加鎖更新dirty和read了,對(duì)應(yīng)上圖中key2的情況
刪除操作
對(duì)于刪除操作弄息,也需要保證數(shù)據(jù)一致性痊班,同樣分為read中有相應(yīng)的key和read中沒(méi)有相應(yīng)的key兩種情況
如果read中沒(méi)有相應(yīng)的key,則直接加鎖后從dirty中刪除key即可
如果read中有相應(yīng)的key摹量,則通過(guò)將entry.p置為nil即可