前言
- 之前面試的時候,有面試官問我產(chǎn)生死鎖的(4個)必要條件,這個我之前有了解過,但是當(dāng)時覺得這些必要條件很官方,也就沒有重視,最后答得支支吾吾,面試官笑著說下去去可以看看,這個在面試中跟智能指針一樣還挺常見的;(面試全程都很順利,卡在這個簡單的問題上確實(shí)不應(yīng)該)
- 由此面試碰到了讓我介紹linux下mutex的種類,這個也是之前略知一二,但是詳細(xì)場景和機(jī)制還是欠缺;
這篇博客根據(jù)上面兩個面試中出現(xiàn)的問題,以本人個人理解進(jìn)行總結(jié):
產(chǎn)生死鎖的4個必要條件
下面四個必要條件不詳細(xì)介紹,如果不理解需要溫習(xí)一下mutex互斥量的作用和lock,unlock加鎖解鎖的底層原理;
(1)互斥條件
:進(jìn)程要求對所分配的資源進(jìn)行排它性控制,即所分配的資源在一段時間內(nèi)某資源僅為一進(jìn)程所占用。
(2)請求與保持條件
:當(dāng)進(jìn)程因請求資源而阻塞時,對已獲得的資源保持不放。(請求新資源的時候,保持已獲得的舊資源)
(3)不剝奪條件
:進(jìn)程已獲得的資源在未使用完之前,不能剝奪,只能在使用完時由自己釋放。
(4)循環(huán)等待條件
:在發(fā)生死鎖時,必然存在一個進(jìn)程–資源的環(huán)形鏈。
預(yù)防(解決)死鎖
破壞4個必要條件中的任一個:
互斥這個規(guī)則,這個為了臨界區(qū)的安全,肯定不能破壞呀;那就破壞后3個;
(1)資源一次性分配
:一次性分配所有資源,用完一次性全部釋放,這樣就不會再有別的進(jìn)程請求時的沖突的現(xiàn)象了:
(2)可剝奪資源
:即當(dāng)某進(jìn)程獲得了部分資源,但得不到其它資源,則釋放(剝奪)已占有的資源(破壞不可剝奪條件)
(3)資源有序分配法
:系統(tǒng)給每類資源賦予一個編號,每一個進(jìn)程按編號遞增的順序請求資源,釋放則相反,就不會出現(xiàn)環(huán)路了(破壞環(huán)路等待條件);
Linux常見的鎖
我們在開發(fā)中常用的鎖主要有互斥鎖,遞歸鎖,自旋鎖,讀寫鎖、樂觀鎖和悲觀鎖這五種:
互斥鎖(普通鎖)
- 正常進(jìn)程間保護(hù)臨界區(qū)的普通鎖,沒有什么特點(diǎn),就是lock()和unlock()的正常上鎖解鎖;
-
當(dāng)線程加鎖失敗時,內(nèi)核會把線程的狀態(tài)從運(yùn)行狀態(tài)設(shè)置為睡眠狀態(tài),然后把CPU切換給其他進(jìn)程運(yùn)行;
-
當(dāng)需要的鎖被其他線程釋放時,之前睡眠狀態(tài)的進(jìn)程會變?yōu)榫途w狀態(tài),然后內(nèi)核會在合適的時候,把CPU切換給該線程運(yùn)行。
普通鎖看似使用簡單,但是由于內(nèi)核幫我們切換進(jìn)程的內(nèi)核態(tài)和用戶態(tài),存在一定的開銷(進(jìn)行了兩次線程上下文切換)
自旋鎖
- 自旋鎖在用戶態(tài)完成加鎖和解鎖的操作,不會主動產(chǎn)生內(nèi)核態(tài)和用戶態(tài)的切換和上下文的切換,所以相比互斥鎖來說,會快一些,開銷也小一些。
自旋鎖的工作原理是在一段時間內(nèi)反復(fù)嘗試獲取被占用的鎖:
當(dāng)加鎖失敗時,互斥鎖用線程切換來應(yīng)對,自旋鎖則用忙等待(反復(fù)嘗試獲取鎖)來應(yīng)對。
互斥鎖和自旋鎖小結(jié)
如果能確定被鎖住的代碼執(zhí)行時間很短,而且多核處理器,就應(yīng)該使用自旋鎖,減少了普通mutex線程上下文切換的開銷;
如果被鎖住的代碼執(zhí)行時間長,而且單核處理器,還是用普通mutex好點(diǎn),因?yàn)檫@時候遞歸鎖反復(fù)長時間輪詢檢測,無疑是浪費(fèi)了cpu資源的舉措。
mutex和自旋鎖是鎖的兩種最基本處理方式,更高級的鎖都會選擇其中一個方式來實(shí)現(xiàn);
比如讀寫鎖既可以選擇基于互斥鎖實(shí)現(xiàn),也可以選擇基于自旋鎖實(shí)現(xiàn)。
遞歸鎖
遞歸鎖只是互斥鎖的一個特例,同樣只能有一個線程訪問該對象;
但遞歸鎖允許同一個線程在 未釋放其擁有的鎖時 反復(fù)對 該鎖 進(jìn)行加鎖操作(普通的鎖就死鎖了)
顯然遞歸鎖的常見場景就是對某一個包含加鎖操作的遞歸類型函數(shù)進(jìn)行調(diào)用; 某線程反復(fù)遞歸這個函數(shù)的時候,使用遞歸鎖就不會像普通mutex那樣第二次遇到加鎖操作就直接進(jìn)入睡眠狀態(tài)掛起了;
讀寫鎖
線程間讀臨界區(qū)資源的時候,不阻塞,只有在有寫的時候才起到mutex的作用,線程間互相阻塞;
這是一種提高效率的鎖,比如mysql中的讀寫鎖,讀的時候因?yàn)椴粫薷呐R界區(qū)的資源,不會產(chǎn)生線程安全問題,因此讀不阻塞效率更高;
寫的時候存在線程安全問題,那就阻塞了;
樂觀鎖與悲觀鎖
悲觀鎖
前面提到的互斥鎖、自旋鎖、讀寫鎖,都屬于悲觀鎖
悲觀鎖認(rèn)為多線程同時對共享資源進(jìn)行修改的事件發(fā)生概率比較高,很容易出現(xiàn)沖突問題,所以訪問共享資源前,必須先上鎖。(做法有點(diǎn)謹(jǐn)慎,悲觀,因此得名)
樂觀鎖(無鎖編程)
- 與悲觀鎖相反,樂觀鎖認(rèn)為沖突的概率很低;
它的工作方式是:先修改完共享資源,再驗(yàn)證這段時間內(nèi)有沒有發(fā)生沖突:
- 如果沒有其他線程在修改資源,那么操作完成;(賺了,沒發(fā)生沖突而且執(zhí)行效率還高)
- 如果發(fā)現(xiàn)有其他線程已經(jīng)修改過這個資源,就直接無腦放棄本次操作。(不虧,有沖突,放棄這次操作以免事態(tài)惡化,之后進(jìn)行重試; 做法很樂觀,因此得名)
樂觀鎖全程沒有加鎖,所以它也叫無鎖編程
樂觀鎖和悲觀鎖小結(jié)
悲觀鎖直接進(jìn)行加鎖操作,進(jìn)行加鎖的操作,適用于沖突事件發(fā)生概率高的場景(這個場景用悲觀鎖得不停地)
樂觀鎖雖然去除了加鎖解鎖的操作,但是一旦發(fā)生沖突,重試的成本其實(shí)也非常高,所以只有在沖突事件發(fā)生的概率非常低,且加鎖成本非常高的場景(比如cpu執(zhí)行效率非常快)時,才考慮使用樂觀鎖。文章來源:http://www.zghlxwxcb.cn/news/detail-833180.html
其他鎖(了解)
還有很多鎖,比如mysql在RR隔離級別下處理幻讀問題使用了row行鎖+GAP間隙鎖等,都是一些高級的適用于某種場景的鎖,所以鎖的可拓展性還是很大的;文章來源地址http://www.zghlxwxcb.cn/news/detail-833180.html
到了這里,關(guān)于Linux產(chǎn)生死鎖的必要條件和常見的鎖種類的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!