sync.Map是一個并發(fā)安全的map,它是通過雙層的數(shù)據(jù)來存儲的,第一層read,可以實現(xiàn)無鎖的讀取,因此sync.Map適合用于讀多寫少的場景,結(jié)構(gòu)如下

個人理解sync.Map的結(jié)構(gòu)有點像常用的緩存設(shè)計,read就是緩存層,它可以進行無鎖的讀??;dirty就是數(shù)據(jù)庫層,它存儲了所有的數(shù)據(jù),需要加鎖進行操作。dirty在一定情況下會提升到read,同時dirty數(shù)據(jù)會從read中進行拷貝
dirty和read的轉(zhuǎn)換
read可以理解為緩存,可以無鎖進行操作,所以很快,dirty即為全量數(shù)據(jù)
- dirty提升為read
Map中有一個變量為misses,它記錄了讀取read沒命中的次數(shù),當(dāng)misses次數(shù)超過了dirty中數(shù)據(jù)個數(shù)的時候,就會將dirty提升為read,同時dirty置為nil。每次讀取read失敗的時候都會進行判斷是否需要將dirty提升到read,升級結(jié)果如下圖所示。其實就是將dirty賦值到read.m

- 從read復(fù)制數(shù)據(jù)到dirty
在寫入數(shù)據(jù)的時候,如果read未命中,會判斷dirty是否為nil,如果是,則會從read中復(fù)制數(shù)據(jù)到dirty中。注意這里expunged就會產(chǎn)生作用了,在復(fù)制的過程中,如果read中某個entry存儲的value的數(shù)據(jù)為nil,說明這個數(shù)據(jù)被刪除了,次數(shù)dirty不會復(fù)制這個entry,同時會將這個entry.p置為expunged。如下圖所示。至于key4和key6為什么為nil,可以看刪除操作

樣例圖

讀取操作
由此可以發(fā)現(xiàn),sync.Map的讀取很簡單,首先無鎖讀取read,如果沒有再去dirty中讀取,其中包括了double check,即加鎖后再check一邊read的數(shù)據(jù)
寫入操作
對于寫入,需要保證read和dirty數(shù)據(jù)一致性,這里有兩種情況,一種是read中有相應(yīng)的key,一種是read中沒有相應(yīng)key
如果read中沒有相應(yīng)的鍵,只需要在dirty中寫入相應(yīng)的key/value就行了,當(dāng)然這里需要加鎖進行寫入即可。例如,如果我們此時需要插入key5,此時read中并沒有key5,則加鎖后往dirty中設(shè)置key5即可,同樣的,key3也是這種情況
如果read中有相應(yīng)的鍵,這里需要對key對應(yīng)的entry中保存的指針進行判斷:
- 如果entry.p不為expunged,可以將其直接指向value即可,因為dirty和read.m的結(jié)構(gòu)都是map[interface{}]*entry,它們都是指針結(jié)構(gòu),因此只需要替換掉entry中的指針,就可以實現(xiàn)dirty和read同時更新。對應(yīng)上圖中key1和key4情況
- 如果entry.p為expunged,那說明這個key在read中存在,但是在dirty中不存在。這種情況就不能直接更新entry.p了,這樣會導(dǎo)致數(shù)據(jù)不同步。這種情況下,就需要加鎖更新dirty和read了,對應(yīng)上圖中key2的情況
刪除操作
對于刪除操作,也需要保證數(shù)據(jù)一致性,同樣分為read中有相應(yīng)的key和read中沒有相應(yīng)的key兩種情況
如果read中沒有相應(yīng)的key,則直接加鎖后從dirty中刪除key即可
如果read中有相應(yīng)的key,則通過將entry.p置為nil即可