死鎖的概念
在多線程編程中,我們為了防止多線程競爭共享資源而導致數據錯亂,都會在操作共享資源之前加上互斥鎖,只有成功獲得到鎖的線程,才能操作共享資源,獲取不到鎖的線程就只能等待,直到鎖被釋放。
那么,當兩個線程為了保護兩個不同的共享資源而使用了兩個互斥鎖,那么這兩個互斥鎖應用不當的時候,可能會造成兩個線程都在等待對方釋放鎖,在沒有外力的作用下,這些次線程會一直相互等待,就沒法繼續運行,這種情況就是發生了死鎖。
死鎖必須同時滿足以下四個條件才會發生:
- 互斥條件
- 持有并等待條件
- 不可剝奪條件
- 環路等待條件
互斥條件
互斥條件式指多個線程不能同時使用同一個資源。
eg:如果線程A已經持有的資源,不能同時被線程B持有,如果線程B請求獲取線程A已經占用的資源,那線程B只能等待,直到線程A釋放了資源。
?持有并等待條件
持有并等待條件是指,當線程 A 已經持有了資源 1 ,又想申請資源 2 ,而資源 2 已經被線程 C 持有了,所以線程 A 就會處于等待狀態,但是線程 A 在等待資源 2 的同時并不會釋放在自己已經持有的資源 1 。
不可剝奪條件
不可剝奪條件是指,當線程已經持有了資源,在自己使用完之前不能被其他線程獲取,線程 B 如果也想使用此資源,則只能在線程 A 使用完并釋放后才能獲取。
環路等待條件
環路等待條件是指,在死鎖發生的時候,兩個線程獲取資源的順序構成了環形鏈。
模擬死鎖問題的產生
用代碼來模擬死鎖問題的產生。
首先,我們創建 2 個線程,分別為線程A和線程B,然后有兩個互斥鎖,分別是 mutex_A 和 mutex_B,代碼如下:
pthread_mutex_t mutex_A = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_B = PTHREAD_MUTEX_INITIALIZER;int main()
{pthread_t tidA, tidB;//創建兩個線程pthread_create(&tidA, NULL, threadA_proc, NULL);pthread_create(&tidB, NULL, threadB_proc, NULL);pthread_join(tidA, NULL);pthread_join(tidB, NULL);printf("exit\n");return 0;
}
線程A函數:
//線程函數 A
void *threadA_proc(void *data)
{printf("thread A waiting get ResourceA \n");pthread_mutex_lock(&mutex_A);printf("thread A got ResourceA \n");sleep(1);printf("thread A waiting get ResourceB \n");pthread_mutex_lock(&mutex_B);printf("thread A got ResourceB \n");pthread_mutex_unlock(&mutex_B);pthread_mutex_unlock(&mutex_A);return (void *)0;
}
可以看到,線程A函數的過程:
- 先獲取互斥鎖 A ,然后睡眠 1 秒
- 再獲取互斥鎖 B ,然后釋放互斥鎖 B
- 最后釋放互斥鎖 A
//線程函數 B
void *threadB_proc(void *data)
{printf("thread B waiting get ResourceB \n");pthread_mutex_lock(&mutex_B);printf("thread B got ResourceB \n");sleep(1);printf("thread B waiting get ResourceA \n");pthread_mutex_lock(&mutex_A);printf("thread B got ResourceA \n");pthread_mutex_unlock(&mutex_A);pthread_mutex_unlock(&mutex_B);return (void *)0;
}
可以看到,線程B函數的過程:
- 先獲取互斥鎖 B ,然后睡眠 1 秒
- 再獲取互斥鎖 A,然后釋放互斥鎖 A
- 最后釋放互斥鎖 B
然后運行這個程序。結果如下:
thread B waiting get ResourceB
thread B got ResourceB
thread A waiting get ResourceA
thread A got ResourceA
thread B waiting get ResourceA
thread A waiting get ResourceB
// 阻塞中。
可以看到線程 B 在等待互斥鎖 A 的釋放,線程 A 在等待互斥鎖 B 的釋放,雙方都在等待對方資源的釋放 -> 產生了死鎖。
避免死鎖問題的發生
前面說到,產生死鎖的四個必要條件是:互斥條件、持有并等待條件、不可剝奪條件、環路等待條件。
那么避免死鎖問題就只需要破壞其中一個條件就可以,最常見的并且可行的就是使用資源有序分配法,來破壞環路等待條件。
資源有序分配法:
線程 A 和線程 B 獲取資源的順序要一樣,當線程 A 是先嘗試獲取資源 A ,然后嘗試獲取資源 B 的時候,線程 B 同樣也是先嘗試獲取資源A ,然后嘗試獲取資源 B 。也就是說,線程 A 和 線程 B 總是以相同的順序申請自己想要的資源。
我們使用資源有序分配法的方式來修改前面發生死鎖的代碼,我們可以不改動線程 A 的代碼。
我們先要清楚線程 A 獲取資源的順序,它是先獲取互斥鎖 A ,然后獲取互斥鎖 B。
所以我們只需要將線程B改成以相同順序的獲取資源,就可以打破死鎖了。
?線程 B 函數改進后的代碼如下:
//線程 B 函數,同線程 A 一樣,先獲取互斥鎖 A,然后獲取互斥鎖 B
void *threadB_proc(void *data)
{printf("thread B waiting get ResourceA \n");pthread_mutex_lock(&mutex_A);printf("thread B got ResourceA \n");sleep(1);printf("thread B waiting get ResourceB \n");pthread_mutex_lock(&mutex_B);printf("thread B got ResourceB \n");pthread_mutex_unlock(&mutex_B);pthread_mutex_unlock(&mutex_A);return (void *)0;
}
總結
簡單來說,死鎖問題的產生是由兩個或者以上線程并行執行的時候,爭奪資源而互相等待造成的。
死鎖只有同時滿足互斥、持有并等待、不可剝奪、環路等待這四個條件的時候才會發生。
所以要避免死鎖問題,就是要破壞其中一個條件即可,最常用的方法就是使用資源有序分配法來破壞環路等待條件。