并發(fā)編程實(shí)戰(zhàn)課03mutex4種易錯(cuò)場(chǎng)景大盤點(diǎn)_第1頁(yè)
并發(fā)編程實(shí)戰(zhàn)課03mutex4種易錯(cuò)場(chǎng)景大盤點(diǎn)_第2頁(yè)
并發(fā)編程實(shí)戰(zhàn)課03mutex4種易錯(cuò)場(chǎng)景大盤點(diǎn)_第3頁(yè)
并發(fā)編程實(shí)戰(zhàn)課03mutex4種易錯(cuò)場(chǎng)景大盤點(diǎn)_第4頁(yè)
并發(fā)編程實(shí)戰(zhàn)課03mutex4種易錯(cuò)場(chǎng)景大盤點(diǎn)_第5頁(yè)
已閱讀5頁(yè),還剩18頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

常見的4Mutex4Lock/Unlock現(xiàn)、Copy已使用的Mutex、重入和死鎖。下面我們一一來看。Lock/UnlockUnlock鎖的Mutex而導(dǎo)致panic。我們先來看看缺少Unlock代碼中有太多的if-else在重構(gòu)的時(shí)候把UnlockUnlock誤寫成了Lock在這種情況下,鎖被獲取之后,就不會(huì)被釋放了,這也就意味著,其它的goroutine我們?cè)賮砜慈鄙貺ock的場(chǎng)景,這就很簡(jiǎn)單了,一般來說就是誤操作刪除了Lock。比如先前使用Mutex都是正常的,結(jié)果后來其他人重構(gòu)代碼的時(shí)候,由于對(duì)代碼不熟悉,或者由于開發(fā)者的馬虎,把Lock調(diào)用給刪除了,或者注釋掉了。比如下面的代碼,mu.Lock()一行代碼被刪除了,直接Unlock一個(gè)未加鎖的Mutex會(huì)panic:12345funcfoo()varmusync.Mutexdefermu.Unlock()}o運(yùn)行的時(shí)候Copy第二種誤用是Copy已使用的Mutex。在正式分析這個(gè)錯(cuò)誤之前,我先交代一個(gè)小知識(shí)點(diǎn),那就是Packagesync的同步原語在使用后是不能的。我們知道Mutex是最常用原因在于,Mutexstate一個(gè)已經(jīng)加鎖的Mutex給一個(gè)新的變量,那么新的剛初始化的變量居然被加鎖了,這顯然不符合你的期望,因?yàn)槟闫谕氖且粋€(gè)零值的Mutex。關(guān)鍵是在并發(fā)環(huán)境下,你根本不知道要的Mutex狀態(tài)是什么,因?yàn)橐腗utex是由其它goroutine并發(fā)的,代代123456789typeCounter{sync.MutexCountint}funcmain()varcCounterdeferfoo(c)// }//這里Counter的參數(shù)是通 funcfoo(c{deferfmt.Println("in2020第12行在調(diào)用foo函數(shù)的時(shí)候,調(diào)用者會(huì)Mutex變量c作為foo函數(shù)的參數(shù),不幸的是,之前已經(jīng)使用了這個(gè)鎖,這就導(dǎo)致,的Counter是一個(gè)帶狀態(tài)怎么辦呢?Go在運(yùn)行時(shí),有死鎖的檢查機(jī)制( eckead() 方法),它能夠發(fā)現(xiàn)鎖的orouie。這個(gè)例子中因?yàn)榱艘粋€(gè)使用了的Muex,導(dǎo)致鎖無法使用,程序處于死鎖的狀態(tài)。程序運(yùn)行的時(shí)候,死鎖檢查機(jī)制能夠發(fā)現(xiàn)這種死鎖情況并輸出錯(cuò)誤信息,如下圖中錯(cuò)誤信息以及錯(cuò)誤堆棧:發(fā)現(xiàn)問題呢?可以使用vet工具,把檢查寫在Makefile文件中,在持續(xù)集成的時(shí)候跑一跑,這樣可以及時(shí)發(fā)現(xiàn)問題,及時(shí)修復(fù)。我們可以使用govet檢查這個(gè)Go文件:你看,使用這個(gè)工具就可以發(fā)現(xiàn)Mutex的問題,錯(cuò)誤信息顯示得很清楚,是在調(diào)用foo函數(shù)的時(shí)候發(fā)生了lockvalue的情況,還告訴我們出問題的代碼行數(shù)以及copylock導(dǎo)致的錯(cuò)誤。那么,vet工具是怎么發(fā)現(xiàn)Mutex使用問題的呢?我?guī)愫?jiǎn)單分析一下題。可以說,只要是實(shí)現(xiàn)了Locker接口,就會(huì)被分析。我們看到,下面的代碼就是確定什么類型會(huì)被分析,其實(shí)就是實(shí)現(xiàn)了Lock/Unlock兩個(gè)方法的Locker接口:代代123456789varlockerType//Constructasync.Lockerinterfacetype.funcinit(){nullary:=types.NewSignature(nil,nil,nil,false)//func()methods:=[]*types.Func{types.NewFunc(token.NoPos,nil,"Lock",nullary),types.NewFunc(token.NoPos,nil,"Unlock",nullary),}lockerType=types.NewInterface(methods,}Locker(WaitGroup),也能被分析。我先賣接下來,我們來討論“重入”這個(gè)問題。在說這個(gè)問題前,我先解釋一下個(gè)概念,叫“可JavaReentrantLockJavaJavaJava當(dāng)一個(gè)線程獲取鎖時(shí),如果沒有其它線程擁有這個(gè)鎖,那么,這個(gè)線程就成功獲取到這個(gè)鎖。之后,如果其它線程再請(qǐng)求這個(gè)鎖,就會(huì)處于阻塞等待的狀態(tài)。但是,如果擁有這把鎖的線程再請(qǐng)求這把鎖的話,不會(huì)阻塞,而是成功返回,所以叫可重入鎖(有時(shí)候也叫做遞歸鎖)。只要你擁有這把鎖,你可以可著勁兒地調(diào)用,比如通過遞歸實(shí)現(xiàn)一些算法,調(diào)用者不會(huì)阻塞或者死鎖。了解了可重入鎖的概念,那我們來看Mutex使用的錯(cuò)誤場(chǎng)景。劃重點(diǎn)了:Mutex不是可想想也不奇怪,因?yàn)镸utex的實(shí)現(xiàn)中沒有記錄哪個(gè)goroutine擁有這把鎖。理論上,任何goroutine都可以隨意地Unlock這把鎖,所以沒辦法計(jì)算重入條件,畢竟,“臣做所以,一旦誤用MutexMutex代代123456789foo(l{foo")}funcbar(l{l.Lock()fmt.Println("inbar")}funcmain()l:=&sync.Mutex{}}寫完這個(gè)Mutex重入的例子后,運(yùn)行一下,你會(huì)發(fā)現(xiàn)類似下面的錯(cuò)誤。程序一直在請(qǐng)求鎖,但是一直沒有辦法獲取到鎖,結(jié)果就是Go運(yùn)行時(shí)發(fā)現(xiàn)死鎖了,沒有其它地方能夠釋Mutexhackergoroutineid,goroutineid,它可以實(shí)現(xiàn)Locker接口。方案二:調(diào)用Lock/Unlock方法時(shí),由goroutine提供一個(gè)token,用來標(biāo)識(shí)它自hackergoroutineid,但是,這樣一來,就不滿足Locker接口了??芍厝腈i(遞歸鎖)解決了代碼重入或者遞歸調(diào)用帶來的死鎖問題,同時(shí)它也帶來了另一個(gè)好處,就是我們可以要求,只有持有鎖的goroutine才能unlock這個(gè)鎖。這也很容易實(shí)現(xiàn),因?yàn)樵谏厦孢@兩個(gè)方案中,都已經(jīng)記錄了是哪一個(gè)goroutine持有這個(gè)鎖。方案一:goroutine這個(gè)方案的關(guān)鍵第一步是獲取goroutineid,方式有兩種,分別是簡(jiǎn)單方式和hacker方簡(jiǎn)單方式,就是通過runtime.Stack方法獲取棧幀信息,棧幀信息里包含goroutineid。你上面panic時(shí)候的貼圖,goroutineid明明白白地顯示在那里。runtime.Stackgoroutinetruegoroutine1....../main.go:19goroutinexxx,xxxgoroutineididfuncGoID()intvarbufn:=runtime.Stack(buf[:],//得到ididField:=strings.Fields(strings.TrimPrefix(string(buf[:n]),"goroutineid,err:=iferr!=nilpanic(fmt.Sprintf("cannotgetgoroutineid:%v", return11了解了簡(jiǎn)單方式,接下來我們來看hackergggoroutineggoroutineTLS第一步:我們先獲取到TLSTLSgoroutineg第三步:再?gòu)膅指針中取出goroutineid。需要注意的是,不同Go版本的goroutine的結(jié)構(gòu)可能不同,所以需要根據(jù)Go的版本進(jìn)行調(diào)整。當(dāng)然了,如果想要搞清楚各個(gè)版本的goroutine結(jié)構(gòu)差異,所涉及的內(nèi)容要重復(fù)發(fā)明,直接使用第的庫(kù)來獲取goroutineid就可以了。好消息是現(xiàn)在已經(jīng)有很多成方法了,可以支持多個(gè)Go版本的goroutineid,給你推薦一個(gè)常用的庫(kù): goroutineid,接下來就是最后的關(guān)鍵一步了,我們實(shí)現(xiàn)一個(gè)可以使用的//RecursiveMutex包裝一個(gè)Mutex,typeRecursiveMutexstruct int64//當(dāng)前持有鎖的goroutinerecursionint32//這個(gè)goroutine67func(m*RecursiveMutex)Lock()gid:=atomic.LoadInt64(&m.owner)==gid //獲得鎖的goroutine第一次調(diào)用,記錄下它的goroutineid,調(diào)用次數(shù)加atomic.StoreInt64(&m.owner,m.recursion=19func(m*RecursiveMutex)Unlock()gid:=//非持有鎖的goroutineifatomic.LoadInt64(&m.owner)!=gidpanic(fmt.Sprintf("wrongtheowner(%d):%d!",m.owner, //調(diào)用次數(shù)減ifm.recursion!=0{//如果這個(gè)goroutine //此goroutineatomic.StoreInt64(&m.owner,-35Mutexowner錄當(dāng)前鎖的擁有者goroutine的id;recursion是輔助字段,用于記錄重入的次數(shù)。有一點(diǎn),我要提醒你一句,盡管擁有者可以多次調(diào)用Lock,但是也必須調(diào)用相同次數(shù)的UnlockLockUnlock方案一是用goroutineid做goroutine的標(biāo)識(shí),我們也可以讓goroutine自己來提供標(biāo)識(shí)。不管怎么說,Go開發(fā)者不期望你利用goroutineid做一些不確定的東西,所以,他們沒有獲取goroutineid的方法。代12//Tokentype代12//TokentypeTokenRecursiveMutexstruct789//請(qǐng)求鎖,需要傳入func(m*TokenRecursiveMutex)Lock(tokenint64)ifatomic.LoadInt64(&m.tokentoken//如果傳入的token和持有鎖的token一致}m.Mutex.Lock()//傳入的token//搶到鎖之后記錄這個(gè)atomic.StoreInt64(&m.token,token)m.recursion=1}//func(m*TokenRecursiveMutex)Unlock(tokenint64)ifatomic.LoadInt64(&m.token)!=token{//釋放其它tokenpanic(fmt.Sprintf("wrongtheowner(%d):%d!",m.token,3456}m.recursion--//當(dāng)前持有這個(gè)鎖的tokenifm.recursion!=0{//}atomic.StoreInt64(&m.token,0)//}我先解釋下什么是死鎖。兩個(gè)或兩個(gè)以上的進(jìn)程(或線程,gorouine)在執(zhí)行過程中,因爭(zhēng)奪共享資源而處于一種互相等待的狀態(tài),如果沒有外部,它們都將無法推進(jìn)下去,此時(shí),我們稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖?;コ忉尦钟泻偷却篻oroutinegoroutine不可:資源只能由持有它的goroutine來釋放環(huán)路等待:一般來說,存在一組等待進(jìn)程,P={P1,P2,…,PN},P1等待P2持有的資源,P2等待P3持有的資源,依此類推,最后是PN等待P1持有的資源,這就形成你看,死鎖問題還真是挺有意思的,所以有很多人研究這個(gè)事兒。一個(gè)經(jīng)典的死鎖問題就是哲學(xué)家就餐問題,我不做介紹了,你可以點(diǎn)擊進(jìn)一步了解。其實(shí),死鎖問題在現(xiàn)實(shí)生活中也比比皆是。舉個(gè)例子。有一次我去派出所開證明,派出所要求物業(yè)先證明我是本物業(yè)的業(yè)主,但是,物業(yè)要我提供派出所的證明,才能給我開物業(yè)證明,結(jié)果就陷入了死鎖狀態(tài)。你可以把派出所和物業(yè)看成兩個(gè)gorouie,派出所證明和物業(yè)證明是兩個(gè)資源,雙方都持有自己的資源而要求對(duì)方的資源,而且自己的資源自己持有,不可。這是一個(gè)最簡(jiǎn)單的只有兩個(gè)goroutine代代1234567packageimport 9funcmain()//var //var varwgwg.Add(2)////派出所處理gofunc()deferwg.Done()// defer //time.Sleep(5*// //物業(yè)處理gofunc()deferwg.Done()// defer //time.Sleep(5*// Go你可以引入一個(gè)第的鎖,大家都依賴這個(gè)鎖進(jìn)行業(yè)務(wù)處理,比如現(xiàn)在推行的一站式政務(wù)服務(wù)中心?;蛘呤墙鉀Q持有等待問題,物業(yè)不需要看到派出所的證明才給開物業(yè)證明,等等。Mutex4還真是有。雖然Mutex使用起來很簡(jiǎn)單,但是,仍然可能出現(xiàn)使用錯(cuò)誤的問題。而且,就連一些經(jīng)驗(yàn)豐富的開發(fā)人員,也會(huì)出現(xiàn)一些Mutex使用的問題。接下來,我就帶你圍觀幾個(gè)非常流行的Go開發(fā)項(xiàng)目,看看這些錯(cuò)誤是怎么產(chǎn)生和修復(fù)的。流行的GoDocker容器是一個(gè)開源的應(yīng)用容器引擎,開發(fā)者可以以統(tǒng)一的方式,把他們的應(yīng)用和依賴包打包到一個(gè)可移植的容器中,然后發(fā)布到任何安裝了docker引擎的服務(wù)器上。Docker是使用Go開發(fā)的,也算是Go的一個(gè)級(jí)產(chǎn)品了,它的Mutex相關(guān)的Bug也不少,我們來看幾個(gè)典型的Bug。issueDocker的issue36114原因在于,hotAddVHDsAtStart方法執(zhí)行的時(shí)候,執(zhí)行了加鎖svm操作。但是,在其中調(diào)用hotRemoveVHDsAtStart方法時(shí),這個(gè)hotRemoveVHDsAtStart方法也是要加鎖svm的。很不幸,Go標(biāo)準(zhǔn)庫(kù)中的Mutex是不可重入的,所以,代碼執(zhí)行到這里,就出現(xiàn)hotRemoveVHDsNoLock避免Mutex的重入。issueissue34881本來是修復(fù)Docker的一個(gè)簡(jiǎn)單問題,如果節(jié)點(diǎn)在初始化的時(shí)候,發(fā)現(xiàn)自己不是一個(gè)在第34行,節(jié)點(diǎn)發(fā)現(xiàn)不滿足條件就返回了,但是,.mu這個(gè)鎖沒有釋放!為什么會(huì)出現(xiàn)這個(gè)問題呢?其實(shí),這是在重構(gòu)或者添加新功能的時(shí)候經(jīng)常犯的一個(gè)錯(cuò)誤,因?yàn)椴惶私馍舷挛?,或者是沒有仔細(xì)看函數(shù)的邏輯,從而導(dǎo)致鎖沒有被釋放。現(xiàn)在的Docer當(dāng)然已經(jīng)沒有這個(gè)問題了。issueMutexissue issueissue72361Mutexdatarace修復(fù)方法也很簡(jiǎn)單,使用互斥鎖即可,這也是我們解決datarace時(shí)常用的方法。issueissue45192也是一個(gè)返回時(shí)忘記Unlock的典型例子,和dockerissue34881犯Bug易,記住晁老師這句話:保證Lock/Unlock成對(duì)出現(xiàn),盡可能采用defermutex.Unlock的方式,把它們成對(duì)、緊湊地寫在一起。除了這些,我也建議你關(guān)注一下其它的Mutex相關(guān)的issue,比 等gRPC是發(fā)起的一個(gè)開源過程調(diào)用(Remoteprocedurecall)系統(tǒng)。該系統(tǒng)基于HTTP/2協(xié)議傳輸,使用ProtocolBuffers作為接口描述語言。它提供Go語言的即使是的系統(tǒng),也有一些Mutex的issueissueissue795是一個(gè)你可能想不到的bug,那就是將Unlock誤寫成了Lock關(guān)于這個(gè)項(xiàng)目,還有一些其他的為了保護(hù)共享資源而添加Mutex的issue,比如1318 etcd是一個(gè)非常知名的分布式一致性的key-value技術(shù),被用來做配置共享和服務(wù)issueissue10419是一個(gè)鎖重入導(dǎo)致的問題。Store方法內(nèi)對(duì)請(qǐng)求了鎖,而調(diào)用的的方法內(nèi)又請(qǐng)求了鎖,這個(gè)時(shí)候,會(huì)導(dǎo)致死鎖,一直等待,解決辦法就是提供不需要加鎖的Compact方法。這節(jié)課,我們學(xué)習(xí)了Muex的一些易錯(cuò)場(chǎng)景,而且,我們還分析了流行的Go開源項(xiàng)目的錯(cuò)誤,我也給你了在開發(fā)中的經(jīng)驗(yàn)總結(jié)。需要強(qiáng)調(diào)的是,手誤和重入導(dǎo)致的死鎖,

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論