1 什么是銀行家算法
1.1 死鎖的定義
死鎖是由信號量引入的一種運行時錯誤。 在一組進程發生死鎖的情況下,這組死鎖線程中的每一個線程,都在等待另一個進程所占有的資源,此時線程被阻塞了,等待一個永遠不為真的條件。
1.2 銀行家算法
銀行家算法是最具代表性的避免死鎖的算法。在銀行中,客戶申請貸款的數量是有限的,每個客戶在第一次申請貸款時要聲明完成該項目所需的最大資金量,在滿足所有貸款要求時,客戶應及時歸還。銀行家在客戶申請的貸款數量不超過自己擁有的最大值時,都應盡量滿足客戶的需要。在這樣的描述中,銀行家就好比操作系統,資金就是資源,客戶就相當於要申請資源的 進程。
它的流程如下。在這里面最關鍵的就是安全狀態的判定,當有進程向系統申請資源時,系統不會直接把資源分配給進程。而是先做一個安全判定,假若分配后系統仍處於安全狀態,則允許分配。那什么是系統安全狀態呢?
1.3 系統安全狀態
1.3.1 定義
系統安全狀態是指系統能夠按某種推進順序(p1、p2...pn)為每個進程pi分配其所需資源,直至每個進程對資源的最大需求。而系統不安全狀態是無法進行推進,使得每個進程pi都能夠獲得其所需的最大需求。 那么關鍵就是如何推進。
1.3.2 進程的推進
其實進程的推進,是對系統資源分配的一種預判或模擬,它需要對所有進程的資源請求進行模擬。原理如下圖。
因為只是一種模擬,其中改變了系統可用資源的數量,所以之后要對系統可用資源進行復原。如果推進失敗,要將已經分配給申請進程的資源合並回系統可用資源,表示申請失敗。
2 設計分析
2.1 系統對象及其交互
進程通過requestResource方法向系統申請資源,申請的資源數量requestNeed在1和maxNeed-ownNeed之間。當已申請的資源數量ownNeed等於maxNeed時,進程對資源利用完成后,可對申請的所有資源進行釋放。
2.2 需要注意的問題
2.2.1 多線程環境需要注意的問題
- 需要保證多線程中,系統分配資源attainResource和釋放資源freeResource調用的原子性,可用互斥量解決;問題也可擴大為對共享的系統對象的互斥訪問。
- 控制台輸出時,需要避免線程之間的多次爭奪導致輸出亂序,盡量使用一個完整語句一次性輸出。例如:在C++中,cout << A << B語句分為了兩次調用,可能在輸出A后,線程發生切換,導致A和B輸出不連續。
- 多線程隨機數問題,可用線程ID作為隨機數種子
2.2.2 其他注意問題
- 銀行家算法進程推進時,在判定過程中,找到符合條件的進程后要再次開始從進程列表的第一個開始找起直到遇到了進程列表的最后一個進程,保證每次查找每個進程都有機會獲得資源。
- 進程列表中進程添加和刪除時機,進程申請成功時無重復地添加,進程釋放資源時從進程列表中刪除該進程記錄
- 資源的改變時機,包括系統可用資源的改變和進程已申請資源的改變
- 方法參數的設置判斷
- 銀行家算法的邏輯
3 核心代碼分析
3.1 進程核心代碼分析
1 bool Process::requestResource(unsigned int num, System &s) 2 { 3 bool rtn = false; 4 int res = 0; 5 if (!setRequestNeed(num)) 6 return false; 7 res = s.attainResource(this); 8 9 if(res == 0) 10 rtn = true; 11 else 12 rtn = false; 13 return rtn; 14 }
進程的申請資源函數,主要是對自身屬性requestNeed進行設置,同時將自身指針傳遞給系統,告知系統分配資源。
將進程對象傳遞給系統對象的好處:
- 系統對象可以方便得知進程對象的最大需求、請求資源數量以及其他進程信息,方便系統銀行家算法的進行,維護進程列表
- 可以減少方法的參數數量
- 減少結果輸出的復雜性
3.2 系統核心代碼
3.2.1 銀行家算法主要邏輯代碼
1 unsigned int System::attainResource(Process *process) 2 { 3 //避免多個線程同時操作 4 wait(); 5 int rtn = 0; 6 unsigned int requestNum = process->getRequestNeed(); 7 unsigned int available_bk = getAvailable(); 8 unsigned int ownNeed_bk = process->getOwnNeed(); 9 if(requestNum <= getAvailable()) 10 { 11 //假設允許該申請,系統資源減少,進程占有資源增多;失敗時要進行恢復 12 setAvailable(getAvailable() - requestNum); 13 process->setOwnNeed(process->getOwnNeed() + requestNum); 14 //進行系統安全狀態的判斷 15 if(banker(process)) 16 { 17 //成功,則將該進程添加入進程列表 18 processList.insert(process); 19 rtn = 0; 20 } 21 else 22 { 23 //失敗,要將數據復原 24 setAvailable(available_bk); 25 process->setOwnNeed(ownNeed_bk); 26 rtn = 1; 27 } 28 //安全序列(包括了不安全序列)輸出 29 printProcessList(std::cout); 30 } 31 else 32 { 33 rtn = 1; 34 } 35 release(); 36 return rtn; 37 }
上面的代碼是銀行家算法的主要邏輯,如下圖。
設計時,為了防止多個線程對共享的系統對象同時操作,需要在函數頭加上wait和函數尾加上release函數,實現互斥。

3.2.2 銀行家算法的安全狀態代碼分析
1 bool System::banker(Process *process) 2 { 3 bool rtn; 4 unsigned int available_bk = getAvailable(); 5 unsigned int num = process->getRequestNeed(); 6 processUid.clear(); 7 for(auto i = processList.begin(); i != processList.end(); ) 8 { 9 if(false == (*i)->isFinish() && (*i)->getMaxNeed() - (*i)->getOwnNeed() <= getAvailable()) 10 { 11 (*i)->setFinish(true); 12 setAvailable(getAvailable()+(*i)->getOwnNeed()); 13 processUid.push_back((*i)->getUid()); 14 i = processList.begin(); 15 } 16 else 17 { 18 ++i; 19 } 20 } 21 rtn = true; 22 for(auto i = processList.begin(); i != processList.end(); ++i) 23 { 24 if((*i)->isFinish() == false) 25 rtn = false; 26 (*i)->setFinish(false); 27 } 28 setAvailable(available_bk); 29 return rtn; 30 }
這個算法的核心就在於每一次都會在進程列表中找出一個符合條件(見代碼)的進程,設為true並且將其所擁有的資源合並到系統可用資源,增加系統可用資源;同時再次在進程列表中查找直到都不符合條件為止。不符合條件中,如果所有進程的finish都為true則代表分配后系統仍處於安全狀態。
注意:在查找前要保證進程列表中的進程的finish都為false。
4 Github代碼
-
https://github.com/cposture/Banker