挑戰408——操作系統(13)——死鎖(deadlock)

死鎖,可以定義爲一組競爭系統資源或者相互通信的進程,相互的“永久性“”的阻塞,若無外力作用,這組進程將永遠沒有辦法繼續執行。很遺憾,目前這種問題沒有一種有效的通用解決方案。
這裏對於死鎖的定義理解要注意死鎖的對象——一組進程,相互競爭。如果是一個進程可能長期或者永久性得不到執行,那麼這個進程是處於飢餓狀態而不是死鎖。同時,飢餓 ≠死鎖!,飢餓現象只是死鎖的一種結果,就是說由於死鎖的出現,導致了死鎖的進程餓死。但是飢餓現象不是一定由於死鎖導致的。比如前面的一些調度算法,也可能造成飢餓,因爲有些程序可能永遠得不到執行,但是不意味着它就死鎖了。用句現在流行的話來說它僅僅是“自閉”了而已。

哲學家進餐問題

在講PV操作的時候,特意沒講這個經典的問題,目的就是讓學到這裏的時候,對死鎖再加深認識,直接舉例子不如分析例子來的好。

問題描述:這個問題由偉大的計算機科學家dijkstra提出並解決。有5個哲學家,他們的生活方式是,思考,進餐。他們圍繞着圓桌而坐,桌子上有5雙筷子和5碗米飯,平時哲學家進行思考,飢餓的時候他拿起左右兩邊的筷子(一根一根的拿起),試圖進餐,進餐完畢後又陷入思考。哲學家只有同時拿到筷子才能進食。如圖:
在這裏插入圖片描述
我們按照之前的模板分析:

  1. 臨界資源分析:顯然筷子是臨界資源,因爲它一次只允許一個哲學家使用。
  2. 交互關係分析:這裏有5個哲學家,也就是有5個一樣的進程,他們對筷子的訪問是互斥的。他們之間沒有一定的先後順序,畢竟不知道誰會先餓。
  3. 思路分析:哲學家能吃到飯的前提是,他手裏有兩根筷子,而拿到筷子的前提是,他左右兩邊的哲學家,恰好在思考。
  4. 信號量設置分析:這裏筷子是臨界資源,每個筷子都是一個臨界資源,對臨界資源的訪問要以互斥的形式訪問,所以要設置5個互斥信號量,信號量較多的情況下,我們用數組表示會方便很多,比如 chopsticks[5] = {1,1,1,1,1,}.因爲沒有同步關係,因此我們也不用設置其他的資源信號量。

僞代碼如下:

//哲學家進餐問題
semaphore chopsticks[5] = {1,1,1,1,1};//定義互斥信號量數組
void philosopher(int i){ //第i個哲學家進程
	while (true){
		P(chopsticks[i]);//申請左邊的筷子
		P(chopsticks[(i + 1)%5]);//申請右邊的筷子
		.......
		..進餐.       //拿到了左右兩根筷子後,進餐
		.......
		V(chopsticks[i]);//放下左邊的筷子
		V(chopsticks[(i + 1)%5);//放下右邊的筷子
		.......
		..思考.  //吃完飯思考
		.......
	}
}

上面的代碼,解決了兩個相鄰的哲學家不會同時進餐的問題,但是思考這樣一個問題。假設所有的哲學家同一時間都餓了呢?我們分析一下,進程同時執行下面的語句:

P(chopsticks[i]);申請左邊的筷子

那麼,現在所有的哲學家手裏都有一根筷子,右邊的筷子已經被別人拿走了,所以誰也沒有辦法進食,每個人都希望右邊的人能放下手中的筷子,陷入了無休止的等待當中,哲學家遲早被餓死。這就是死鎖現象。這樣的理解很是深刻。
那麼先就事論事,先解決這個問題再說:
提供一種思路,既然這樣全都得餓死,那不如就先讓一個人吃完,吃完後把筷子讓出來。也就是說,只有當哲學家兩邊的筷子都能用的時候,才允許它吃飯。那麼具體怎麼實現呢?其實不難,我們只要在某個哲學家拿筷子的時候,其他的哲學家都不能拿筷子,也就是說取筷子這個動作是互斥的。於是有了下面的改進代碼:

//哲學家進餐問題
semaphore chopsticks[5] = {1,1,1,1,1};//定義互斥信號量數組
semaphore mutex = 1;//設置使用筷子的互斥信號量
void philosopher(int i){ //第i個哲學家進程
	while (true){
		P(mutex);//申請使用筷子操作
		P(chopsticks[i]);//申請左邊的筷子
		P(chopsticks[(i + 1)%5]);//申請右邊的筷子
		.......
		..進餐.       //拿到了左右兩根筷子後,進餐
		.......
		V(chopsticks[i]);//放下左邊的筷子
		V(chopsticks[(i + 1)%5];//放下右邊的筷子
		V(mutex);//筷子使用完畢,放回原位
		.......
		..思考.
		.......
	}
}

死鎖產生的原因

死鎖產生的原因主要有四個方面:資源不足,進程推進順序非法,資源的使用。

  1. 資源不足:通常系統中擁有的不可剝奪的資源,數量不足以滿足多個進程的需要,使得進程在運行過程中因爲競爭資源而導致競爭資源陷入僵局,導致死鎖。(比如競爭打印機,剛剛哲學家問題的筷子
  2. 進程推進順序非法:進程中申請和釋放資源的順序安排不當(如將生產者消費者之間的PV操作的順序顛倒)。如果進程P不是同時需要兩個資源,先使用一個再申請一個,這樣就不會產生死鎖。
  3. 資源的使用。系統中的資源主要分兩大類:可重複資源,可消耗資源。
    可重複資源包括:處理機,I/O通道,設備文件,以及數據庫等等,這種資源,進程得到後,再釋放,供其他進程再次使用。
    可消耗資源包括:中斷,信號量,緩衝區。當一個進程使用該資源後,便不復存在。
    就生產者消費者問題而言,按照順序先P(mutex)也是基於此考慮的。

解決死鎖的方法是:預防,檢測和避免。(這個很重要,常識)

死鎖的必要條件

  1. 互斥。一段時間內,某種資源只能由一個進程佔有,此時其他要求使用該資源的進程只能阻塞。
  2. 請求與保持。當進程已經佔有了至少一個進程,又提出新的資源要求,而該被請求的資源又被其他進程佔用,此時進程阻塞,但是對它持有的資源,保持不放。
  3. 不可剝奪。進程已經獲得資源,在它使用完畢前不能被剝奪,只能使用完畢後自己釋放。
  4. 環路條件。存在一個與資源的環形鏈,設鏈中的每個進程都在等待一個被佔用的資源。(就是哲學家問題中的,每個哲學家都手持一根筷子的情況)。
    在這裏插入圖片描述
    一般情況下,前面三種情況都是合理的。進程間的互斥,是爲了保證再現性,請求與保持是正常的運行,對於一些資源,比如打印機,是不能剝奪使用的。
    123是死鎖的必要條件,而4是123的潛在的結果。當前面的123某一種情況與4同時出現的時候,表明進程間發生了死鎖

解決死鎖的方法是:預防,檢測和避免。(這個很重要,常識)

死鎖的預防

死鎖的預防可以從4個必要條件入手。

  • 對於互斥,這是程序併發必不可少的一種機制,無法避免
  • 對於請求與保持。出現問題的原因是因爲,運行過程中自己的資源不夠,而向外申請造成的。所以我們可以預先分配。即要求進程一次性請求完所有資源,若不能滿足,就阻塞這個進程。直到它所有的資源都滿足爲止。這就要求預知進程運行所需要的總資源數。
    缺點:進程將會被延遲運行,資源被嚴重浪費。而且最重要的是,進程只有在運行的時候才知道需要哪些資源
  • 對於不可剝奪,最直接的方式就是剝奪阻塞進程的資源,但是代價太大,會導致進程無限期推遲運行。
  • 對於環路,那麼破壞環路的條件。可以採用有序分配策略
    如果一個進程已經分配到了某種類型的資源,它接下來請求的資源,只能是那些排在這個資源之後的資源。(即按一定順序來申請資源)

死鎖的檢測

死鎖檢測算法主要是檢查系統中的進程是否有循環等待,將系統總的進程和資源的申請和分配描述成一幅有向圖,稱爲資源分配圖。通過檢查有向圖中是否有環來判斷死鎖的存在。
在這裏插入圖片描述

用方框代表資源,方框裏面的小圓圈代表的是資源數,P1,P2是進程。
從進程出的邊爲請求邊,從進程入的邊爲分配邊
那麼,採用拓撲排序的方式來判斷圖中是否有環。關於拓撲排序 看這個:數據結構——圖(9)——拓撲排序與DFS

死鎖定理:系統處於死鎖狀態等價於狀態S的資源分配圖是不可以被簡化的

死鎖的預防

銀行家算法(下篇詳細介紹)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章