1. 程式人生 > >多線程編程之線程死鎖問題

多線程編程之線程死鎖問題

ica roc 常見 處理 帶來 模塊 現象 時間 多線程編程

在多線程編程中,除了要解決數據訪問的同步與互斥之外,還需要解決的重要問題就是多線程的死鎖問題。所謂死鎖: 是指兩個或兩個以上的進程(線程)在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外部處理作用,它們都將無限等待下去。

一、死鎖原因與形成條件

  死鎖形成的原因:

  1. 系統資源不足
  2. 進程(線程)推進的順序不恰當;
  3. 資源分配不當

  死鎖形成的條件:

  1. 互斥條件:所謂互斥就是進程在某一時間內獨占資源。
  2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:進程已獲得資源,在末使用完之前,不能強行剝奪。
  4. 循環等待條件:若幹進程之間形成一種頭尾相接的循環等待資源關系。

  從編程經驗上來講,形成死鎖的一般原因有以下幾種:

  1. 個人使用鎖的經驗差異。
  2. 程序模塊使用鎖的差異。
  3. 工程代碼版本之間的差異。
  4. 工程代碼分支之間的差異。
  5. 修改代碼和重構代碼帶來的差異。

二、常見死鎖形成的場景

  死鎖形成的常見情況有以下幾種:

2.1 忘記釋放鎖

技術分享圖片
void data_process()
{
    EnterCriticalSection();

    if(/* error happens, forget LeaveCriticalSection */)
        return;

    LeaveCriticalSection();
}
技術分享圖片

2.2 單線程重復申請鎖

技術分享圖片
void sub_func()
{
    EnterCriticalSection();
    do_something();
    LeaveCriticalSection();
}

void data_process()
{
    EnterCriticalSection();
    sub_func();
    LeaveCriticalSection();
}
技術分享圖片

2.3 多線程多鎖申請

技術分享圖片
void data_process1()
{
    EnterCriticalSection(&cs1);  // 申請鎖的順序有依賴
    EnterCriticalSection(&cs2);
    do_something1();
    LeaveCriticalSection(&cs2);
    LeaveCriticalSection(&cs1);
}

void data_process2()
{
    EnterCriticalSection(&cs2);  // 申請鎖的順序有依賴
    EnterCriticalSection(&cs1);
    do_something2();
    LeaveCriticalSection(&cs1);
    LeaveCriticalSection(&cs2);
}
技術分享圖片

2.4 環形鎖申請

/* 多個線程申請鎖的順序形成相互依賴的環形:
*             A   -  B
*             |      |
*             C   -  D
*/

三、死鎖的避免策略

  死鎖的代價是非常大的,有時候很難檢測排查,因此需要在編程過程中盡可能的避免發生死鎖。編程中為了避免死鎖應該遵循如下策略

  1. 在編寫多線程程序之前,首先編寫正確的程序,然後再移植到多線程。
  2. 時刻檢查自己寫的程序有沒有在跳出時忘記釋放鎖。
  3. 如果自己的模塊可能重復使用一個鎖,建議使用嵌套鎖
  4. 對於某些鎖代碼,不要臨時重新編寫,建議使用庫裏面的鎖,或者自己曾經編寫的鎖。
  5. 如果某項業務需要獲取多個鎖,必須保證鎖的按某種順序獲取,否則必定死鎖。
  6. 編寫簡單的測試用例,驗證有沒有死鎖。
  7. 編寫驗證死鎖的程序,從源頭避免死鎖。

多線程編程之線程死鎖問題