盡量不加鎖以生產(chǎn)者-消費(fèi)者模型為例:如果多個(gè)消費(fèi)者之間可以做到互不關(guān)聯(lián)的處理業(yè)務(wù)邏輯,那么應(yīng)該盡量避免他們之間產(chǎn)生關(guān)聯(lián) 。其業(yè)務(wù)處理過(guò)程中需要的各個(gè)對(duì)象,宜各自一份 。
對(duì)數(shù)據(jù)加鎖,而不是對(duì)過(guò)程加鎖擁有JAVA經(jīng)驗(yàn)的同學(xué)要特別小心這一點(diǎn):JAVA中,在方法上加上個(gè)關(guān)鍵字就能實(shí)現(xiàn)互斥,但這時(shí)非常不好的設(shè)計(jì)方式 。只需要對(duì)并發(fā)環(huán)境下產(chǎn)生沖突的變量加鎖即可,代碼及其不沖突的變量都是不必要加鎖的 。
更進(jìn)一步,如果存在多個(gè)沖突的變量,且在程序中不同的位置發(fā)生沖突,那么可以對(duì)特定的一組變量定義一個(gè)特定的鎖,而不是使用一把統(tǒng)一的大鎖來(lái)進(jìn)行互斥——盡量使用多個(gè)鎖,讓沖突進(jìn)一步減小 。
讀多寫(xiě)少的場(chǎng)景考慮讀寫(xiě)鎖某些讀寫(xiě)的場(chǎng)景下,讀是可以并發(fā)的,而寫(xiě)是互斥的 。這種場(chǎng)景下,讀寫(xiě)鎖是比互斥鎖更好的選擇 。
原子操作基礎(chǔ)的原子操作技巧 var value int64 = 0 atomic.AddInt64(&value, 1) // 原子加 atomic.AddInt64(&value, -1) // 原子減 var n uint64 = 1 atomic.AddUint64(&n, 1) atomic.AddUint64(&n, ^uint64(0)) // 原子減1,無(wú)符號(hào)類(lèi)型,使用反碼來(lái)減 newValue := atomic.LoadInt64(&value) // 內(nèi)存屏障,避免亂序執(zhí)行,并且同步CPU cache和內(nèi)存 atomic.StoreInt64(&value, newValue) oldValue := atomic.SwapInt64(&value, 0) // 獲取當(dāng)前值,并清零原子操作就能搞定的并發(fā)場(chǎng)景,就不要再使用鎖 。
自旋鎖golang里面哪來(lái)的自旋鎖?
其實(shí)我們可以自己寫(xiě)一個(gè):
var globalValue int64 = 0func xxx(newValue int64){ oldValue := atomic.LoadInt64(&globalValue) // 相當(dāng)于使用 memory barrier 指令,避免指令亂序 for !atomic.CompareAndSwapInt64(&globalValue, oldValue, newValue) { // 自旋等待,直到成功oldValue = atomic.LoadInt64(&globalValue) // 失敗后,說(shuō)明那一瞬間值被修改了 。需要重新獲取最新的值// 其他數(shù)值操作的準(zhǔn)備 }}以上是無(wú)鎖數(shù)據(jù)結(jié)構(gòu)的經(jīng)典套路 。
atomic.Value: 用于并發(fā)場(chǎng)景下需要切換的對(duì)象有的對(duì)象很基礎(chǔ),可能需要頻繁訪問(wèn),且有時(shí)又會(huì)發(fā)生引用的切換 。比如程序中的全局配置,很多地方都會(huì)引用,有時(shí)配置更新后,又會(huì)切換為最新的配置 。
這種情況下,加鎖的成本太高,不加鎖又會(huì)帶來(lái)風(fēng)險(xiǎn) 。因此,使用sync.Value來(lái)保存全局配置的數(shù)據(jù)是個(gè)不錯(cuò)的選擇 。
type Configs map[string]stringvar globalConfig atomic.Valuefunc GetConfig() Configs { v, ok := globalConfig.Load().(Configs) if ok{return v } return map[string]string{}}func SetConfig(cfg Configs){ globalConfig.Store(cfg)}并發(fā)容器sync.Map并發(fā)map設(shè)計(jì)得很精巧,用起來(lái)也很簡(jiǎn)單 。不過(guò)很可惜,sync.Map沒(méi)有那么快,要避免將sync.Map用在程序的關(guān)鍵路徑上 。
當(dāng)然,我上述的觀點(diǎn)的區(qū)分點(diǎn)是:這是業(yè)務(wù)程序還是系統(tǒng)程序,如果是系統(tǒng)程序,盡量不要用 。我實(shí)際使用中發(fā)現(xiàn),sync.Map會(huì)導(dǎo)致CPU消耗高,且GC壓力增大 。
RoaringBitmap(或類(lèi)似實(shí)現(xiàn))對(duì)某些特定的場(chǎng)景,可以做到很少的鎖,很小的內(nèi)存,比如存儲(chǔ)大量UINT64類(lèi)型的集合這一點(diǎn),RoaringBitmap是個(gè)非常好的選型 。
VictoriaMetrics中有一個(gè)RoaringBitmap實(shí)現(xiàn)的組件,叫做uint64set 。具體介紹請(qǐng)見(jiàn):《vm中仿照RoaringBitmap的實(shí)現(xiàn):uint64set》(本人) 。
經(jīng)驗(yàn)總結(jié)擴(kuò)展閱讀
- 持續(xù)集成指南:GitLab 的 CI/CD 工具配置與使用
- 信用卡怎么查消費(fèi)明細(xì)
- 消費(fèi)貸款申請(qǐng)產(chǎn)生的費(fèi)用高嗎
- oppo賬號(hào)的姓名怎么修改
- 租房可以換鎖芯嗎合法么 租房換鎖費(fèi)用誰(shuí)來(lái)承擔(dān)
- 特斯拉可以用家用電充電嗎
- 拆線多久可以用祛疤膏?
- 衛(wèi)生間和廚房用什么瓷磚?
- 肉蓯蓉副作用是什么
- 計(jì)米器怎么設(shè)置參數(shù)
