在 【操作系統】線程同步、線程互斥、原子操作 一問中,使用到了 鎖 這個東西,當一個線程進入臨界區後把門鎖起來,其他線程跑到這裏來發現鎖被使用了,就等着鎖被釋放。
當然,這是理想的情況下,但實際開發中還會出現下面的情況。
死鎖栗子
mutex; //代表一個全局互斥對象
void A() {
mutex.lock();
//這裏操作共享數據
B();
mutex.unlock();
return;
}
void B()
{
mutex.lock();
//這裏操作共享數據
mutex.unlock();
return;
}
可別想的是自己寫不出這樣的代碼來,哈哈哈。
死鎖概念
死鎖是指在一組進程中的各個線程均佔有不會釋放的資源,但因互相申請被其他進程所站用不會釋放的 資源而處於的一種永久等待狀態。
產生死鎖的 4 個必要條件
1、互斥條件:一個資源每次只能被一個執行流使用。
2、請求與保持條件:一個執行流因請求資源而阻塞時,對已獲得的資源保持不放。
3、不剝奪條件:一個執行流已獲得的資源,在末使用完之前,不能強行剝奪。
4、循環等待條件:若干執行流之間形成一種頭尾相接的循環等待資源的關係。
常見場景
1、一個線程加鎖後再次嘗試加鎖。
2、兩個線程 t1, t2 有兩把鎖 m1, m2,線程 t1 先去獲取鎖 m1 再去獲取鎖 m2,線程 t2 先去獲取鎖 m2 再去獲取鎖 m1,造成死鎖。
3、多個線程多個鎖。哲學家就餐問題!
哲學家就餐問題
哲學家就餐問題: 比如5個哲學家和五根筷子。
哲學家行爲:1. 思考(啥都不幹) 2. 喫雞
極端情況,五個哲學家同時拿起自己左手邊的筷子,然後哲學家就無法再拿起右手的筷子。
那麼當所有哲學家都不願意放下左手的筷子,就會造成所有線程死鎖。
解決方法:
方法1
做出約定:先給每個筷子編號,約定哲學家每次先拿編號小的筷子。若所有哲學家依次拿筷子,到最後的哲學家肯定就會拿另一根筷子,就可以避免出現死鎖。這樣的順序約定是破除死鎖的常見辦法。破除死鎖中的"環路等待"條件。
比如現在我給每個筷子給上編號,當一號哲學家拿的時候先拿1號筷子,二號哲學家先拿2號筷子。。以此類推當五號哲學家拿筷子的時候,不會再去拿5號筷子了,而是嘗試去拿1號筷子,但是1號筷子已經被一號哲學家拿過了,所以五號哲學家就需要等待。四號哲學家就可以再拿5號筷子可以喫飯了,等他喫完釋放4 5 號筷子,3號哲學家就可以喫飯了。。以此類推。這樣就解決了。
方法2
信號量 PV 操作。
搞一個信號量初始化爲 4(比鎖數量少 1)
每個哲學家拿筷子的時候進行 P 操作,當第 5 個人在申請鎖的時候信號量爲 0 就不可以申請了。
如何避免死鎖
1、破壞死鎖的 4 個條件。
2、加鎖順序一致。
3、避免鎖未釋放。
4、資源一次性分配。
5、算法(死鎖檢測算法、銀行家算法)。
參考文章:
https://blog.csdn.net/weixin_42678507/article/details/90906296
更多:
C++ 死鎖案例:
https://blog.csdn.net/i_likechard/article/details/78206018
EOF