中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何預測讀寫鎖的死鎖問題

發布時間:2021-09-10 11:50:46 來源:億速云 閱讀:158 作者:柒染 欄目:大數據

如何預測讀寫鎖的死鎖問題,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

1.死鎖(Deadlock)

死鎖在日常生活中并不鮮見。生活在大城市的人都或多或少經歷過下圖所示的場景。在環島或者十字路口出現的這種情況就是死鎖。也許其中有車壞了,但是絕大多數車子是可以運行的。可是因為每輛車都得等著前車走動它才能走動,所有車都走不動,或者更一般地講它們不能取得進展(Make Forward Progress)。這種情況發生的原因是車輛的等待構成了循環,在這個循環中每輛車的狀態都是等待前車,因此所有車都等不到到它所要等待的。這種車輛死鎖狀態會持續惡化并產生嚴重的后果:首先造成路口交通堵塞,而堵塞如果進一步擴大會導致大面積交通癱瘓。車輛死鎖很難自愈,通過自身走出死鎖狀態非常困難或者需要很長時間,一般都只能通過人工(如交通警察)干預才能解決。

如何預測讀寫鎖的死鎖問題 圖1:環島道路的交通堵塞(圖片來源于網絡)

在多線程或者分布式系統程序中,死鎖也會發生。其本質和上述的路口車輛堵塞是一樣的,都是因為參與者構成了循環等待,使得所有參與者都等不到想要的結果,從而永遠等在那里不能取得進展。Linux 內核當然也會發生死鎖,如果核心部分(Core),如調度器和內存管理,或者子系統,如文件系統,發生死鎖,都會導致整個系統不可用。

死鎖是隨機發生的。就像上圖中環島的情況一樣,環島就在那里而死鎖并不是總在發生。但是環島本身就是死鎖隱患,尤其在交通壓力比較大的路口,環島會比較容易產生死鎖。而如果這種路口設計成交通信號燈就會好很多,如果設計成立交橋則又會好很多。在程序中,我們把可能產生死鎖的場景稱作潛在死鎖(Potential Deadlock Scenario),而把即將發生或正在發生的死鎖稱為死鎖實例(Concrete Deadlock)。

如何對付死鎖一直是學術界和應用領域積極研究和解決的問題。我們可以將對死鎖的解決方案粗略地分為:死鎖發現(Detection)、死鎖避免(Prevention)和死鎖預測(Prediction)。死鎖發現是指在在程序運行中發現死鎖實例;死鎖避免則是在發現死鎖實例即將生成時進一步防止這個實例;而死鎖預測則是通過靜態或者動態方法找出程序中的潛在死鎖,從而從根本上預先消除死鎖隱患。

2.鎖的死鎖和 Lockdep

在死鎖中,因為用鎖(Lock)不當而導致的死鎖是一個重要死鎖來源。鎖是同步的一種主要手段,用鎖是不可避免的。對于復雜的同步關系,鎖的使用會比較復雜。如果使用不當很容易造成鎖的死鎖。從等待的角度來說,鎖的死鎖是由于參與線程等待鎖的釋放,而這種等待構成了等待循環,如 ABBA 死鎖:

如何預測讀寫鎖的死鎖問題 圖2:兩線程ABBA死鎖

其中,線程中的黑色箭頭代表線程當前執行語句,紅色箭頭表示線程語句之間的等待關系。可以看到,紅色箭頭構成了一個圓圈(或者循環)。再一次回顧潛在死鎖和死鎖實例,如果這兩個線程執行的時間稍有改變,那么很有可能不會發生死鎖實例,比如如果讓 Thread1 執行完這一段代碼 Thread2 才開始執行。但是這樣的用鎖行為(Locking Behavior)毫無疑問是一個潛在死鎖。

進一步可以看出,如果我們能夠追蹤并分析程序的用鎖行為就有可能預測死鎖或者找出潛在死鎖,而不是等死鎖發生時才能檢測出死鎖實例。Linux 內核的 Lockdep 工具就是去刻畫內核的用鎖行為進而預測潛在死鎖并報告出來。

Lockdep 能夠刻畫出一類鎖(Lock Class)的行為,主要是通過記錄一類鎖中所有鎖實例的加鎖順序(Locking Order),即如果一個線程拿著鎖A,在沒有釋放前又去拿鎖B,那么鎖A和鎖B就有一個 A->B 的加鎖順序,在 Lockdep 中這個加鎖順序被稱為:鎖依賴 (Lock Dependency)。同樣的,對于 ABBA 類型的死鎖,我們并不需要 Thread1 和 Thread2 恰好產生一個死鎖實例,只要有線程產生了 A->B 加鎖順序行為,又有線程產生了一個 B->A 的加鎖順序行為,那么這就構成了一個潛在死鎖,如下圖所示:

如何預測讀寫鎖的死鎖問題 圖3:線程ABBA加鎖順序

由此推廣開來,我們可以把所有的加鎖順序(即鎖依賴)記錄和保存下來,構成一個加鎖順序圖(Graph)。其中,如果有鎖依賴 A->B ,又有鎖依賴 B->C ,那么由于鎖依賴的關系(Relation)是傳遞的(Transitive),因此我們還可以得到鎖依賴 A->C 。 A->B 和 B->C 稱為直接依賴(Direct Dependency),而 A->C 稱為間接依賴(Indirect Dependency)。對于每一個新的直接鎖依賴,我們去檢查這個依賴是否和圖中已經存在的鎖依賴構成一個循環,如果是的話,那么我們就可以預測產生了一個潛在死鎖。

3.讀寫鎖(Read-write Lock)

剛才我們所指的鎖都是互斥鎖(Exclusive Lock)。讀寫鎖是一種更復雜的鎖,或者說一種通用的鎖(General Lock),我們可以認為互斥鎖是只用寫鎖的讀寫鎖。只要沒有寫鎖或者寫鎖的爭搶,讀鎖允許讀者(Reader)同時持有。 Linux 內核中有多種讀寫鎖,主要包括: rwsem 、 rwlock 和 qrwlock 等。問題是,讀寫鎖會讓死鎖預測變得異常復雜, Lockdep 就不能支持這幾種讀寫鎖,因此 Lockdep 在使用過程中會產生一些相關的錯誤假陽性(False Positive)死鎖預測和錯誤假陰性(False Negative)死鎖預測。這個問題已經存在超過10年以上,我們提出一個通用的鎖的死鎖預測算法,并證明這個算法解決了讀寫鎖的死鎖預測問題。

4.通用鎖的死鎖預測算法(General Deadlock Prediction For Locks)

在描述這個算法的過程中,我們通過提出幾個引理(Lemma)來解釋或者證明我們所提出的死鎖預測的有效性。

▍引理1:在引入了讀寫鎖后,鎖的加鎖順序循環是潛在死鎖的必要條件,但不是充分條件。并且,一個潛在死鎖能且只能最早在最后一個加鎖順序(或鎖依賴)即將生成死鎖循環的時候被預測出來。

基于引理1,解決死鎖預測問題就是在最后一個拿鎖順序(即鎖依賴)形成等待圓環(循環)時,通過某種方法計算出這個等待圓環是否構成潛在死鎖,而我們的任務就是找到這個方法(算法)。

▍引理2:兩個虛擬線程 T1 和 T2 可以用來表示所有的死鎖場景。

對于任何一個死鎖實例來說,假定有 n 個線程參與到這個死鎖實例中,這 n 個線程表示為:

T1,T2,…,Tn

考慮 n 的情況:

如果 n=1:這種死鎖即線程自己等待自己,在 Lockdep 中被稱為遞歸死鎖(Recursion Deadlock)。由于檢查這種死鎖較為簡單,因此在下面的算法中忽略這種特殊情況。 如果 n>1:這種死鎖在 Lockdep 中被稱為翻轉死鎖(Inversion Deadlock)。對于這種情況,我們將這 n 個線程分成兩組,即 T1,T2,…,Tn-1 和 Tn ,然后把前一組中的所有鎖依賴合并在一起并假想所有這些依賴存在于一個虛擬的線程中,于是得到兩個虛擬線程 T1 和 T2 。

這就是引理2中所述的兩個虛擬線程。基于引理2,我們提出一個死鎖檢查雙線程模型(Two-Thread Model)來表示內核的加鎖行為:

T1 :當前檢查鎖依賴之前的所有鎖依賴,這些依賴形成了一個鎖依賴圖。 T2 :當前的待檢查的直接鎖依賴。

基于引理2和死鎖檢查雙線程模型,我們可以得到如下引理:

▍引理3:任何死鎖都可以轉化成 ABBA 類型。

基于上述3個引理,我們可以進一步將死鎖預測問題描述為,當我們得到一個新的直接鎖依賴 B->A 時,我們將這個新依賴設想為 T2 ,而之前的所有鎖依賴都存在于一個設想的 T1 產生的一個鎖依賴圖中,于是死鎖預測就是檢查 T1 中是否存在 A->B 的鎖依賴,如果存在即存在死鎖,否則就沒有死鎖并將 T2 合并到 T1 中。如下圖所示:

如何預測讀寫鎖的死鎖問題 圖4:T1的鎖依賴圖和T2的直接鎖依賴

在引入了讀寫鎖之后,鎖依賴還取決于其中鎖的類型,即讀或者寫類型。我們根據 Linux 內核中互斥鎖和讀寫鎖的設計特性,引入一個鎖互斥表來表示鎖之間的互斥關系:

如何預測讀寫鎖的死鎖問題 表1:讀寫鎖互斥關系表

其中,遞歸讀鎖(Recursive-read Lock)是一種特殊的讀鎖,它能夠被同一個線程遞歸地拿。下面我們首先提出一個簡單算法(Simple Algorithm)。基于雙線程模型,給定 T1 和 T2 ,和 ABBA 鎖:

如何預測讀寫鎖的死鎖問題 圖5:基于雙線程模型的簡單算法

簡單算法的步驟如下:

如果 X1.A 和 X1.B 是互斥的且 X2.A 和 X2.B 是互斥的,那么 T1 和 T2 構成潛在死鎖。

否則, T1 和 T2 不構成潛在死鎖。

從簡單算法中可以看出,鎖類型決定了鎖之間的互斥關系,而互斥關系是檢查死鎖的關鍵信息。對于讀寫鎖來說,鎖類型可能在程序執行過程中變化,那么如何記錄所有的鎖類型呢?我們基于鎖類型的互斥性,即鎖類型的互斥性由低到高:遞歸讀鎖 < 讀鎖 < 寫鎖(互斥鎖),提出了鎖類型的升級(Lock Type Promotion)。在程序執行過程中,如果碰到了高互斥性的鎖類型,那么我們將鎖依賴中的鎖類型升級到高互斥性的鎖依賴。鎖類型升級如圖所示:

如何預測讀寫鎖的死鎖問題 圖6:鎖類型的升級

其中 RRn 表示遞歸讀鎖n(Recursive-read Lock n) ,Rn表示讀鎖n(Read Lock n),Wn代表寫鎖或者互斥鎖n(Write Lock n)。下面 Xn 則表示任意鎖n (即遞歸讀、讀或者寫鎖)。

但是,如上簡單算法并不能處理所有的死鎖預測情況,比如下面這個案例就會躲過簡單算法,但事實上它是一個潛在死鎖:

如何預測讀寫鎖的死鎖問題 圖7:簡單算法失敗案例

在這個案例中, X1 和 X3 是互斥的從而這個案例構成了潛在死鎖。但是簡單算法在檢查 RR2->X1 時(即 T2 為 RR2->X1 ),根據簡單算法能夠找到 T1 中有 X1->RR2 ,但是由于 RR 和 RR 不具有互斥性,因而錯誤認定這個案例不是死鎖。分析這個案例為什么得出錯誤結論,是因為真正的死鎖 X1X3X3X1 中的 X3->X1 是間接鎖依賴,而間接依賴被簡單算法漏掉了。

這個問題的更深層次原因是因為互斥鎖之間只有互斥性,因此只要有 ABBA 就是潛在死鎖,并不需要檢查 T2 的間接鎖依賴。而在有讀鎖的情況下,這一條件不復存在,因此就要去考慮 T2 中的間接鎖依賴。

▍引理4:對于直接鎖依賴引入的間接鎖依賴,如果間接鎖依賴構成死鎖,那么直接鎖依賴仍然是關鍵的。

引理4是引理1的引申,根據引理1,這個直接鎖依賴一定是形成鎖循環的那個最后鎖依賴,而引理4說明通過這個鎖依賴一定可以通過某種方法判斷出鎖循環是否是潛在死鎖。換句話說,通過修改和加強之前提出的簡單算法,新的算法一定能夠解決這個問題。但是問題是,原先 T2 中直接鎖依賴可能進一步生成了很多間接鎖依賴,我們如何才能找到那個最終產生潛在死鎖的間接鎖依賴呢?更進一步,我們首先需要重新定義 T2 ,再在這個 T2 中找出所有的間接鎖依賴,那么 T2 的邊界是什么?如果把 T2 擴展到整個鎖依賴圖,那么算法復雜度會提高非常多,甚至可能超出 Lockdep 的處理能力,讓 Lockdep 變得實際上不可用。

▍引理5:T2 只需要擴展到當前線程的拿鎖棧(Lock Stack)。

根據引理5,我們首先修改之前提出的雙線程模型為:

T1:當前檢查直接鎖依賴之前的所有鎖依賴,這些依賴形成了一個圖。 T2:當前的待檢查的線程的鎖棧。

根據引理5和新的雙線程模型,我們在簡單算法的基礎上提出如下最終算法(Final Algorithm):

繼續搜索鎖依賴圖即 T1 尋找一個新的鎖依賴循環。 在這個新的循環中,如果有 T2 中的其他鎖存在,那么這個鎖和 T2 中的直接鎖依賴構成一個間接鎖依賴,檢查這個間接鎖依賴是否構成潛在死鎖。 如果找到潛在死鎖,那么算法結束,如果沒有到算法轉到1直到搜索完整個鎖依賴圖為止。

這個最終算法能解決之前出現漏洞的案例嗎?答案是可以的,具體檢查過程如圖所示:

如何預測讀寫鎖的死鎖問題 圖8:簡單算法的失敗案例解決過程

然而,對于所有其他情況,引理5是正確的嗎?為什么最終算法能夠工作呢?我們通過如下兩個引理來證明最終算法中的間接鎖依賴是必要且充分的。

▍引理6:檢查 T2 當中的間接鎖依賴是必要的,否則簡單算法已經解決了所有問題。

引理6說明由于讀寫鎖的存在,不能只檢查直接鎖依賴。

▍引理7:T2 的邊界就是當前線程的鎖棧,這是充分的。

根據引理2和引理3,任何死鎖都可以轉化成雙線程 ABBA 死鎖,并且 T1 只能貢獻 AB,T2 必須貢獻 BA 。在這里,T2 不僅僅是一個虛擬線程,也是一個實際存在的物理線程,因此 T2 需要且只需要檢查當前線程。

到這里,一個通用的讀寫鎖死鎖預測算法就描述并非正式證明完畢。

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

温州市| 望奎县| 洱源县| 镇安县| 万载县| 宜君县| 错那县| 阳曲县| 永定县| 永丰县| 嘉鱼县| 江孜县| 许昌县| 芦溪县| 抚松县| 丰宁| 榆林市| 阳朔县| 潍坊市| 广宗县| 太保市| 新干县| 琼中| 雅安市| 东光县| 定结县| 凌海市| 嘉祥县| 绿春县| 龙岩市| 威海市| 古田县| 巴东县| 梅州市| 忻城县| 巫溪县| 秦安县| 巴塘县| 大邑县| 兴国县| 黎城县|