首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C語言線程死鎖實戰(二):雙互斥量交叉加鎖導致的死鎖與代碼演示

     編輯:關於C

接續上一回「線程死鎖(一):同一線程重複加鎖」,本文深入剖析第二種更隱蔽、更常見的死鎖場景——兩個線程交叉持有兩個互斥量並互相等待對方釋放。通過完整的 C 代碼(基於 pthread)演示死鎖發生、現象觀察,並說明為何這種寫法不可取。

死鎖場景還原

  • 兩個全局互斥量:mutex_value1、mutex_value2。
  • 線程 1:先鎖 mutex1,執行讀操作,再鎖 mutex2,最後解鎖(順序:1→2)。
  • 線程 2:先鎖 mutex2,執行寫操作,再鎖 mutex1,最後解鎖(順序:2→1)。
  • 若線程 1 持有 1 等 2,線程 2 持有 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);

線程 1:讀線程(先鎖 1 後鎖 2)

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);
}

線程 2:寫線程(先鎖 2 後鎖 1)

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
                      循環等待,系統死鎖

為什麼這樣寫是不合理的?

  1. 破壞同步目的:互斥量是為了保護共享數據,交叉加鎖導致線程長時間持有多把鎖,並發度極低。
  2. 違反加鎖順序原則:所有線程應按固定全局順序獲取多把鎖(如先 1 後 2),打破「循環等待」必要條件。
  3. 難以調試:死鎖發生具備隨機性,依賴線程調度,常在高併發、特定時序下才暴露。

預防死鎖的鐵律

策略說明
統一加鎖順序給所有互斥量編號,所有線程按編號遞增順序加鎖
使用 trylock + 回退pthread_mutex_trylock 非阻塞嘗試,失敗則釋放已持有鎖並重試
鎖超時機制使用 pthread_mutex_timedlock 避免無限等待
減少鎖持有範圍只在訪問共享數據的最小代碼段加鎖,避免在鎖內調用複雜函數/睡眠
層級化鎖設計大鎖保護大結構,小鎖保護細粒度字段,避免多鎖嵌套

總結

雙互斥量交叉加鎖是多線程開發中最經典的死鎖陷阱。本文代碼專為演示而設計,實際項目中絕不可這樣寫。理解死鎖的四個必要條件(互斥、佔有並等待、不可搶佔、循環等待),並始終遵循「統一加鎖順序」原則,是編寫健壯多線程程序的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved