Hello倦春,各位小伙伴大家好,我是小棧君落剪,近期氣溫有所下降睁本,希望各位小伙伴記得防寒保暖,不要感冒了哦忠怖。
本期分享主題是關(guān)于go語言中的鎖的應(yīng)用場(chǎng)景呢堰,以及為各位小伙伴介紹實(shí)戰(zhàn)應(yīng)用中最為廣泛的讀寫鎖和互斥鎖。
互聯(lián)網(wǎng)生態(tài)的日益繁榮凡泣,人們的生活便利得到了極大的提高枉疼,通過網(wǎng)上操作我們基本上可以實(shí)現(xiàn)很多需求。
網(wǎng)站瘋狂訪問的背后應(yīng)對(duì)的是一波接一波的挑戰(zhàn)鞋拟。所以在應(yīng)對(duì)系統(tǒng)的穩(wěn)定和并發(fā)的時(shí)候骂维,程序中的“鎖”就孕育而生。
互斥鎖
在編程中贺纲,引入了對(duì)象互斥鎖的概念航闺,來保證共享數(shù)據(jù)操作的完整性。每個(gè)對(duì)象都對(duì)應(yīng)于一個(gè)可稱為" 互斥鎖" 的標(biāo)記猴誊,這個(gè)標(biāo)記用來保證在任一時(shí)刻潦刃,只能有一個(gè)線程訪問該對(duì)象。
也就是將共享資源變成獨(dú)占資源稠肘「GΓ互斥鎖的應(yīng)用場(chǎng)景通常是寫大于讀操作的,它不同于讀寫鎖的讀者隨意訪問项阴,而寫者只有一個(gè)滑黔。
它代表的資源就是一個(gè)笆包,不管是讀者還是寫者,只要誰擁有了它略荡,那么其他人就只有等待解鎖后庵佣,隱約在腦海中浮現(xiàn)出“寶刀屠龍,誰與針鋒”的話語汛兜。
其實(shí)我們可以很形象的理解一下互斥鎖巴粪,資源就好比是一個(gè)廁所,很多人都想上廁所粥谬,但是坑位只有一個(gè)肛根,那么誰獲取了互斥鎖,那么誰就有權(quán)利進(jìn)去漏策,其他人只有在門口排隊(duì)等待派哲。也就是我們通常所說的阻塞。
在go語言的sync包中也是有對(duì)于互斥鎖的解釋掺喻,互斥鎖的結(jié)構(gòu)體很簡(jiǎn)單芭届,并且他的接口就只有一個(gè)加鎖和一個(gè)解鎖操作。
當(dāng)value為空時(shí)就是一個(gè)解鎖的互斥鎖感耙,也就是其他人都可以來使用褂乍。并且當(dāng)互斥鎖第一次使用的時(shí)候就不能再被復(fù)制。
并且在代碼中也有很詳細(xì)的說明即硼,有興趣的小伙伴可以參考代碼源碼進(jìn)行了解逃片。大致的意思就是說互斥鎖有兩種模式,正常和饑餓模式谦絮。
在正常的模式下采用的是FIFO模式即先進(jìn)先出题诵,但是會(huì)被等待者喚醒。沒有擁有互斥鎖的等待者會(huì)同新來的協(xié)程進(jìn)行競(jìng)爭(zhēng)层皱,獲取鎖的使用權(quán)。
但是新來的協(xié)程擁有一個(gè)優(yōu)勢(shì)就是他們是運(yùn)行在CPU上的赠潦,而之前有可能會(huì)有很多進(jìn)程或協(xié)程需要被喚醒叫胖,所以他們有可能在毫秒之間就被人插隊(duì)了。
也就是新來的不需要被喚醒直接獲取到她奥。如果一個(gè)在1ms的時(shí)間內(nèi)沒有獲取到互斥量瓮增,那么它將進(jìn)入到饑餓模式。
也就是說在互斥鎖的饑餓模式下他會(huì)進(jìn)行有序的交接哩俭。也就是會(huì)將互斥鎖的所有權(quán)進(jìn)行移交到排在前面的等待者绷跑。
而新來的等待者想要獲取互斥鎖就只有乖乖排隊(duì)。他也不會(huì)試圖去搶占互斥鎖凡资。如果說在饑餓模式下他是最后一個(gè)互斥鎖的擁有者的話砸捏,或是等待少于1ms獲取鎖,那么他就會(huì)重新轉(zhuǎn)變?yōu)檎DJ健?/p>
其實(shí)在正常模式下協(xié)程會(huì)有更好的性能,但是饑餓模式是為了預(yù)防更多不確定的情況垦藏。
互斥鎖實(shí)戰(zhàn):
我們先進(jìn)行一個(gè)簡(jiǎn)單的模擬梆暖,定義個(gè)Map進(jìn)行模擬數(shù)據(jù)庫對(duì)象,制定一個(gè)Map的切片來做為數(shù)據(jù)對(duì)象的id和name掂骏,并且進(jìn)行數(shù)據(jù)的初始化轰驳,當(dāng)我們開啟10個(gè)并發(fā)請(qǐng)求進(jìn)行修改某一個(gè)值的時(shí)候。
我們可以看到最終的結(jié)果是修改成功了弟灼。但是各位小伙伴我們是真的就沒有任何問題了么级解?
在go語言中我們其實(shí)可以檢查是否有問題可以使用 go build -race 來進(jìn)行數(shù)據(jù)競(jìng)爭(zhēng)檢測(cè)。
[小知識(shí):以后小伙伴在不確定的情況下都可以進(jìn)行使用命令進(jìn)行驗(yàn)證田绑,當(dāng)我們不確定打包工具有哪些命令的時(shí)候勤哗,我們可以用go help build 來進(jìn)行查看]
雙擊后我們可以看到出現(xiàn)了以下問題,看來當(dāng)初我們直接使用goland進(jìn)行運(yùn)行看到的表現(xiàn)往往有一絲絲的不妥當(dāng)辛馆,還是太年輕了鞍陈!
我們也可以看到在點(diǎn)擊文件后得到的結(jié)論是確實(shí)對(duì)于共享資源的搶占是會(huì)導(dǎo)致問題的昙篙,而且在go語言中我們也是很友好的進(jìn)行了提前的測(cè)試腊状,避免了在線上問題排查。
對(duì)于線程相關(guān)的問題在生產(chǎn)上其實(shí)排查相對(duì)而言是有點(diǎn)困難的苔可。在文件中也可以明確的顯示出問題出現(xiàn)在17行缴挖,也就是我們?cè)趯?duì)于Map切片進(jìn)行操作的時(shí)候。
所以我們針對(duì)多并發(fā)訪問的時(shí)候需要對(duì)共享資源進(jìn)行加鎖處理焚辅,目前模擬的應(yīng)用場(chǎng)景是寫大于讀的時(shí)候映屋。我們使用互斥鎖進(jìn)行相應(yīng)的操作。
得到的結(jié)論和之前的一樣同蜻,但是我們同樣需要對(duì)這段程序進(jìn)行g(shù)o build -race 操作棚点。
最終得到了正確無誤的操作。以上就是我們關(guān)于互斥鎖的初步使用湾蔓。按照使用規(guī)范來講瘫析,不管是互斥鎖和讀寫鎖,我們都應(yīng)該盡可能的對(duì)于小范圍的進(jìn)行使用默责,在關(guān)鍵處進(jìn)行使用贬循,避免程序擁有大量的阻塞。
讀寫鎖
讀寫鎖實(shí)際是一種特殊的自旋鎖桃序,它把對(duì)共享資源的訪問者劃分成讀者和寫者杖虾,讀者只對(duì)共享資源進(jìn)行讀訪問,寫者則需要對(duì)共享資源進(jìn)行寫操作媒熊。
這種鎖相對(duì)于自旋鎖而言奇适,能提高并發(fā)性坟比,因?yàn)樵诙嗵幚砥飨到y(tǒng)中,它允許同時(shí)有多個(gè)讀者來訪問共享資源滤愕,最大可能的讀者數(shù)為實(shí)際的邏輯CPU數(shù)温算。
寫者是排他性的,一個(gè)讀寫鎖同時(shí)只能有一個(gè)寫者或多個(gè)讀者(與CPU數(shù)相關(guān))间影,但不能同時(shí)既有讀者又有寫者注竿。在讀寫鎖保持期間也是搶占失效的。
如果讀寫鎖當(dāng)前沒有讀者魂贬,也沒有寫者巩割,那么寫者可以立刻獲得讀寫鎖,否則它必須自旋在那里付燥,直到?jīng)]有任何寫者或讀者宣谈。
如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖键科,否則讀者必須自旋在那里闻丑,直到寫者釋放該讀寫鎖。
所以針對(duì)于系統(tǒng)中需要讀多寫少的情況下勋颖,我們就需要使用到讀寫鎖進(jìn)行應(yīng)對(duì)程序的并發(fā)嗦嗡,保護(hù)程序的安全、穩(wěn)定饭玲、高效的運(yùn)行侥祭。
在go語言中內(nèi)置包中已經(jīng)實(shí)現(xiàn)了關(guān)于讀寫鎖操作,我們?cè)谙到y(tǒng)中需要使用可以很方便的進(jìn)行操作茄厘。
在代碼注釋中很明確的可以看出矮冬,在go語言中的sync包中的RWMutex就是一個(gè)讀寫互斥鎖,該鎖可以有任意數(shù)量的讀者或是只有一個(gè)寫者持有次哈,形象的說就好比一場(chǎng)電影可以有無數(shù)的人來看胎署,但是導(dǎo)演就只有一個(gè)。
當(dāng)值為空的時(shí)候窑滞,他是處于解鎖狀態(tài)硝拧,并且在讀寫鎖第一次使用的時(shí)候是不允許被拷貝的。如果一個(gè)協(xié)程(goroutine)持有讀的權(quán)限葛假,另一個(gè)協(xié)程進(jìn)行鎖操作,那么沒有任何一個(gè)協(xié)程能夠再持有讀鎖滋恬,除非被釋放聊训。
當(dāng)然開發(fā)者也提醒了禁止進(jìn)行遞歸讀鎖定,是為了保證鎖能夠一直可用恢氯。大概意思就是說這部電影不能讓你一個(gè)人獨(dú)自看带斑,畢竟獨(dú)樂樂不如眾樂樂嘛鼓寺。
在代碼中我們常用的兩個(gè)操作就是Lock 和Unlock 即加解鎖操作,通過代碼中我們可以知道go語言最多讀者可以高達(dá)10億個(gè)勋磕,已經(jīng)能夠完美的滿足到我們的業(yè)務(wù)需求了妈候。
讀寫鎖實(shí)戰(zhàn):
接下來我們將模擬一下關(guān)于網(wǎng)頁中的讀寫鎖操作,讀寫鎖的應(yīng)用場(chǎng)景多數(shù)是類似于朋友圈或微博的并發(fā)場(chǎng)景下的讀大于寫的場(chǎng)景挂滓,所以我們用讀寫兩個(gè)循環(huán)協(xié)程進(jìn)行模擬數(shù)據(jù)庫的請(qǐng)求操作苦银,并是使用count進(jìn)行原子計(jì)數(shù),最終得到的結(jié)果如下:
當(dāng)然如果我們依舊關(guān)閉鎖操作會(huì)得到相同的結(jié)果么赶站?
使用goland執(zhí)行后的結(jié)果和之前加鎖的狀態(tài)是是沒有什么區(qū)別幔虏,但是真的沒有什么區(qū)別么?
讓我們使用一下go build -race 命令贝椿,最后得到的結(jié)果如下:
表面風(fēng)輕云淡的情況下內(nèi)部已經(jīng)翻江倒海了想括,所以小棧君在這里也是奉勸各位三思而后行。
讀寫鎖與互斥鎖性能大比拼
讀寫鎖和互斥鎖的各自實(shí)戰(zhàn)情況已經(jīng)初略的給各位分享了一遍烙博,總體而言用法是比較簡(jiǎn)單的瑟蜈,并且有興趣的小伙伴可以看看go語言的內(nèi)置包,后續(xù)我也會(huì)陸續(xù)為大家分享關(guān)于go語言實(shí)現(xiàn)二叉樹渣窜,鏈表結(jié)構(gòu)等文章铺根,讓大家更加深入的感受到go語言的魅力。
當(dāng)然我也在籌劃其他語言的分享图毕,所以各位小伙伴夷都,如果你喜歡我的文章,麻煩分享并關(guān)注小棧君哦予颤。
回歸正題囤官,我們?cè)谑褂酶髯缘氖褂脠?chǎng)景下并沒有感受到讀寫鎖魚互斥鎖性能上有多大的區(qū)別。所以小棧君接下來就一個(gè)場(chǎng)景分別使用兩個(gè)鎖來進(jìn)行模擬請(qǐng)求計(jì)數(shù)蛤虐,得出結(jié)論党饮。
我們首先使用讀寫鎖進(jìn)行模擬如圖所示:
我們先定義讀寫鎖、數(shù)據(jù)庫數(shù)據(jù)驳庭、還有相關(guān)的原子計(jì)數(shù)器刑顺。這里有一個(gè)小知識(shí)點(diǎn),我們?cè)谟?jì)數(shù)的時(shí)候用了atomic饲常,這樣可以保證計(jì)數(shù)的準(zhǔn)確和安全蹲堂。然后開啟了一個(gè)寫協(xié)程和一百個(gè)讀協(xié)程。
為了加大請(qǐng)求力度我們有在每個(gè)協(xié)程里開啟了無線循環(huán)讀取贝淤,用1毫秒模擬查詢時(shí)間柒竞,用10毫秒模擬寫時(shí)間。最終得出的結(jié)論是計(jì)數(shù)器計(jì)算到了16萬接近17萬次播聪。
然后相同的代碼我們僅僅只修改了一下鎖的機(jī)制朽基,將讀寫鎖改成了互斥鎖布隔,各位可以看到效果就是代碼執(zhí)行了1695次,相差是非常大的稼虎。
以上就是關(guān)于go語言中的互斥鎖和讀寫鎖的分享衅檀。事實(shí)證明,只有鎖用的對(duì)霎俩,我們就可以早些下班啦哀军。
好了,今天的分享就到這啦茸苇,如果你喜歡我的分享排苍,麻煩你點(diǎn)擊一個(gè)好看或贊,我是小棧君学密,不定期分享IT干貨淘衙,包括但不限于區(qū)塊鏈、大數(shù)據(jù)腻暮、Python彤守、go、等系列專題哭靖。原創(chuàng)不易具垫,更新較慢,多多包涵试幽。希望與你共同成長(zhǎng)筝蚕。我們下期再見啦,拜了個(gè)拜~