在 Linux 中,死鎖(Deadlock) 是指多個進程或線程因為競爭資源而相互等待,導致所有相關進程或線程都無法繼續執行的狀態。死鎖是一種嚴重的系統問題,會導致系統資源浪費,甚至系統崩潰。
死鎖的定義
死鎖是指兩個或多個進程或線程在執行過程中,因為爭奪資源而造成的一種互相等待的現象。如果沒有外部干預,這些進程或線程將永遠無法繼續執行。
死鎖的四個必要條件
死鎖的發生需要同時滿足以下四個條件(稱為 Coffman 條件):
互斥條件(Mutual Exclusion):
資源一次只能被一個進程或線程占用。
例如,鎖(如互斥鎖)就是一種互斥資源。
占有并等待(Hold and Wait):
進程或線程持有至少一個資源,同時等待獲取其他被占用的資源。
非搶占條件(No Preemption):
?已分配給進程或線程的資源不能被強制剝奪,必須由其自行釋放。
循環等待條件(Circular Wait):
存在一個進程或線程的循環鏈,每個進程或線程都在等待下一個進程或線程所占用的資源。
只有當這四個條件同時滿足時,死鎖才會發生。
死鎖的示例
以下是一個典型的死鎖示例:
#include <pthread.h> #include <stdio.h>pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;void* thread1_func(void* arg) {pthread_mutex_lock(&mutexA); // 線程1持有mutexAsleep(1); // 模擬一些操作pthread_mutex_lock(&mutexB); // 線程1嘗試獲取mutexBprintf("Thread 1 is running.\n");pthread_mutex_unlock(&mutexB);pthread_mutex_unlock(&mutexA);return NULL; }void* thread2_func(void* arg) {pthread_mutex_lock(&mutexB); // 線程2持有mutexBsleep(1); // 模擬一些操作pthread_mutex_lock(&mutexA); // 線程2嘗試獲取mutexAprintf("Thread 2 is running.\n");pthread_mutex_unlock(&mutexA);pthread_mutex_unlock(&mutexB);return NULL; }int main() {pthread_t tid1, tid2;pthread_create(&tid1, NULL, thread1_func, NULL);pthread_create(&tid2, NULL, thread2_func, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0; }
線程1持有
mutexA
并等待mutexB
。線程2持有
mutexB
并等待mutexA
。兩個線程互相等待,導致死鎖。
死鎖的影響
資源浪費:死鎖會導致相關進程或線程無法繼續執行,占用系統資源。
系統崩潰:如果死鎖涉及關鍵資源,可能導致整個系統無法正常運行。
難以調試:死鎖通常難以復現和調試,尤其是在復雜的多線程程序中。
如何避免死鎖
鎖順序:確保所有線程以相同的順序獲取鎖。
超時機制:為鎖操作設置超時(如
pthread_mutex_timedlock
),避免無限等待。避免嵌套鎖:盡量減少鎖的嵌套使用。
死鎖檢測:使用工具或算法檢測死鎖并采取措施。
資源分配策略:使用資源分配算法(如銀行家算法)避免死鎖。
死鎖檢測與恢復
檢測:
使用工具(如
gdb
、valgrind
)分析程序運行狀態。實現死鎖檢測算法(如圖的環路檢測)。
恢復:
強制終止一個或多個進程或線程。
回滾操作,釋放資源并重新分配。
線程阻塞
在 Linux 中,死鎖和阻塞是兩個不同的概念,盡管它們都與資源的競爭和等待有關,但它們的表現和原因有顯著區別:
阻塞(Blocking)
定義:阻塞是指一個進程或線程因為等待某個資源(如鎖、I/O 操作、信號量等)而暫時無法繼續執行,進入等待狀態。
原因:
等待獲取鎖(如互斥鎖、讀寫鎖)。
等待 I/O 操作完成(如讀取文件、網絡數據)。
等待信號量或其他同步機制。
特點:
阻塞是暫時的,一旦資源可用,進程或線程會被喚醒并繼續執行。
阻塞是正常的同步機制,用于協調多個進程或線程對共享資源的訪問。
阻塞不會導致系統無法運行,只是當前任務暫時停止。
示例:
pthread_mutex_lock(&mutex); // 如果鎖被其他線程持有,當前線程會阻塞 // 臨界區代碼 pthread_mutex_unlock(&mutex);
區別總結
總之,阻塞是正常的同步行為,而死鎖是需要避免的系統錯誤。
線程饑餓
一個線程持有鎖一直不釋放,其他線程一直在等待這個鎖,這種情況不滿足鎖的四個必要條件,算是死鎖嗎?
比如如果一個線程持有鎖后進入死循環,且其他線程嘗試獲取該鎖。
具體過程:
線程 A 持有鎖后進入死循環,永遠不會釋放鎖。
線程 B 嘗試獲取該鎖,但由于鎖被線程 A 持有,線程 B 會一直阻塞等待。
如果還有其他線程也嘗試獲取該鎖,它們同樣會阻塞等待。
最終,這些線程會因為無法獲取鎖而永久阻塞
示例代碼如下:
#include <pthread.h> #include <stdio.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* thread_func(void* arg) {pthread_mutex_lock(&mutex); // 線程 A 獲取鎖while (1) {// 死循環,永遠不會釋放鎖}pthread_mutex_unlock(&mutex); // 這行代碼永遠不會執行return NULL; }int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);pthread_mutex_lock(&mutex); // 主線程嘗試獲取鎖,會一直阻塞printf("This will never be printed.\n");pthread_mutex_unlock(&mutex);pthread_join(tid, NULL);return 0; }
線程 A 獲取鎖后進入死循環,永遠不會釋放鎖。
主線程嘗試獲取鎖時會被阻塞
這種情況下,雖然不滿足死鎖的四個必要條件,但它確實會導致類似死鎖的現象,通常稱為**資源饑餓(Resource Starvation)或活鎖(Livelock)**的一種表現。下面詳細分析:
1. 這種情況的特點
一個線程持有鎖后一直不釋放。
其他線程因為無法獲取鎖而一直等待。
不滿足死鎖的四個必要條件(特別是循環等待條件),因為沒有多個線程相互等待。
2. 為什么不是死鎖?
死鎖的四個必要條件之一是循環等待,即存在一個進程或線程的循環鏈,每個進程或線程都在等待下一個進程或線程所占用的資源。而在你的描述中:
只有一個線程持有鎖,其他線程在等待這個鎖。
沒有形成循環等待鏈,因此不滿足死鎖的定義。
3. 這種情況的名稱
這種情況通常被稱為資源饑餓(Resource Starvation):
一個線程獨占資源(如鎖),導致其他線程無法獲取資源,從而無法繼續執行。
資源饑餓不一定是死鎖,但它會導致系統性能下降或部分功能失效。
總結下線程饑餓和死鎖的區別
比較明顯的現象就是,線程饑餓時通常會有部分線程還能執行,但是死鎖時,涉及到的所有線程都無法執行。
更多參考:
五、面試官:你講一下線程死鎖、饑餓和死循環的區別以及死鎖的處理? 我:滔滔不絕...._死鎖和循環依賴的區別-CSDN博客
常見問題
死鎖、資源饑餓、CPU飆高、內存泄漏、內存溢出、棧溢出