接續上一回「線程死鎖(一):同一線程重複加鎖」,本文深入剖析第二種更隱蔽、更常見的死鎖場景——兩個線程交叉持有兩個互斥量並互相等待對方釋放。通過完整的 C 代碼(基於 pthread)演示死鎖發生、現象觀察,並說明為何這種寫法不可取。
mutex_value1、mutex_value2。mutex1,執行讀操作,再鎖 mutex2,最後解鎖(順序:1→2)。mutex2,執行寫操作,再鎖 mutex1,最後解鎖(順序:2→1)。#if MUTEX_ENABLE pthread_mutex_t mutex_value1; pthread_mutex_t mutex_value2; #endif // main 中初始化 res = pthread_mutex_init(&mutex_value1, NULL); res = pthread_mutex_init(&mutex_value2, NULL); // main 結束前銷毀 res = pthread_mutex_destroy(&mutex_value1); res = pthread_mutex_destroy(&mutex_value2);
void *thread_func1(void *param) {
int i = 0, res = 0;
pthread_t tid = pthread_self();
printf("Thread ID::%u -----------S----------\n", (unsigned)tid);
while (i++ < 4) {
#if MUTEX_ENABLE
res = pthread_mutex_lock(&mutex_value1); // 鎖 1
if (res) printf(" mutex1 lock failed\n");
#endif
read_data("Thread_1");
#if MUTEX_ENABLE
res = pthread_mutex_lock(&mutex_value2); // 鎖 2 —— 可能阻塞
if (res) printf(" mutex2 lock failed\n");
#endif
#if MUTEX_ENABLE
res = pthread_mutex_unlock(&mutex_value2);
if (res) printf(" mutex2 unlock failed\n");
res = pthread_mutex_unlock(&mutex_value1);
if (res) printf(" mutex1 unlock failed\n");
#endif
sleep(2);
}
printf("Thread ID::%u -----------E----------\n", (unsigned)tid);
pthread_exit(&status);
}
void *thread_func2(void *param) {
int i = 0, res = 0;
pthread_t tid = pthread_self();
printf("Thread ID::%u -----------S----------\n", (unsigned)tid);
while (i++ < 4) {
#if MUTEX_ENABLE
res = pthread_mutex_lock(&mutex_value2); // 鎖 2
if (res) printf(" mutex2 lock failed\n");
#endif
write_data("Thread_2");
#if MUTEX_ENABLE
res = pthread_mutex_lock(&mutex_value1); // 鎖 1 —— 可能阻塞
if (res) printf(" mutex1 lock failed\n");
#endif
#if MUTEX_ENABLE
res = pthread_mutex_unlock(&mutex_value1);
if (res) printf(" mutex1 unlock failed\n");
res = pthread_mutex_unlock(&mutex_value2);
if (res) printf(" mutex2 unlock failed\n");
#endif
sleep(1);
}
printf("Thread ID::%u -----------E----------\n", (unsigned)tid);
pthread_exit(&status);
}
Create first thread Create second thread Thread ID::3076344640 -----------S---------- [Thread_1] start reading data Thread ID::3067951936 -----------S---------- [Thread_2] start writing data [Thread_1] data = 0 [Thread_1] end reading data [Thread_2] data = 1 [Thread_2] end writing data mutex2 can't be destroyed // 程序卡死,無法銷毀互斥量,進程異常終止
時間 →
Thread 1: lock(m1) ── read ── lock(m2) [阻塞] ────────────────────────
Thread 2: lock(m2) ── write ── lock(m1) [阻塞] ────────────
↑ ↑
持有 m2 等 m1 持有 m1 等 m2
循環等待,系統死鎖
| 策略 | 說明 |
|---|---|
| 統一加鎖順序 | 給所有互斥量編號,所有線程按編號遞增順序加鎖 |
| 使用 trylock + 回退 | pthread_mutex_trylock 非阻塞嘗試,失敗則釋放已持有鎖並重試 |
| 鎖超時機制 | 使用 pthread_mutex_timedlock 避免無限等待 |
| 減少鎖持有範圍 | 只在訪問共享數據的最小代碼段加鎖,避免在鎖內調用複雜函數/睡眠 |
| 層級化鎖設計 | 大鎖保護大結構,小鎖保護細粒度字段,避免多鎖嵌套 |
雙互斥量交叉加鎖是多線程開發中最經典的死鎖陷阱。本文代碼專為演示而設計,實際項目中絕不可這樣寫。理解死鎖的四個必要條件(互斥、佔有並等待、不可搶佔、循環等待),並始終遵循「統一加鎖順序」原則,是編寫健壯多線程程序的基石。