1. 程式人生 > >Linux執行緒間死鎖分析

Linux執行緒間死鎖分析

死鎖 (deadlocks): 是指兩個或兩個以上的程序(執行緒)在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序(執行緒)稱為死鎖程序(執行緒)。 由於資源佔用是互斥的,當某個程序提出申請資源後,使得有關程序(執行緒)在無外力協助下,永遠分配不到必需的資源而無法繼續執行,這就產生了一種特殊現象死鎖。

一種交叉持鎖死鎖的情形,此時執行程式中兩個或多個執行緒發生永久堵塞(等待),每個執行緒都在等待被其它執行緒佔用並堵塞了的資源。例如,如果執行緒 1 鎖住了記錄 A 並等待記錄 B,而執行緒 2 鎖住了記錄 B 並等待記錄 A,這樣兩個執行緒就發生了死鎖現象。在計算機系統中 , 如果系統的資源分配策略不當,更常見的可能是程式設計師寫的程式有錯誤等,則會導致程序因競爭資源不當而產生死鎖的現象。

產生死鎖的四個必要條件

(1) 互斥條件:一個資源每次只能被一個程序(執行緒)使用。
(2) 請求與保持條件:一個程序(執行緒)因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件 : 此程序(執行緒)已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 迴圈等待條件 : 多個程序(執行緒)之間形成一種頭尾相接的迴圈等待資源關係。

圖 1. 交叉持鎖的死鎖示意圖:

註釋:在執行 func2 和 func4 之後,子執行緒 1 獲得了鎖 A,正試圖獲得鎖 B,但是子執行緒 2 此時獲得了鎖 B,正試圖獲得鎖 A,所以子執行緒 1 和子執行緒 2 將沒有辦法得到鎖 A 和鎖 B,因為它們各自被對方佔有,永遠不會釋放,所以發生了死鎖的現象。

使用 pstack 和 gdb 工具對死鎖程式進行分析

pstack 在 Linux 平臺上的簡單介紹

pstack 是 Linux(比如 Red Hat Linux 系統、Ubuntu Linux 系統等)下一個很有用的工具,它的功能是列印輸出此程序的堆疊資訊。可以輸出所有執行緒的呼叫關係棧。

gdb 在 Linux 平臺上的簡單介紹

GDB 是 GNU 開源組織釋出的一個強大的 UNIX 下的程式除錯工具。Linux 系統中包含了 GNU 除錯程式 gdb,它是一個用來除錯 C 和 C++ 程式的偵錯程式。可以使程式開發者在程式執行時觀察程式的內部結構和記憶體的使用情況 .

gdb 所提供的一些主要功能如下所示:

1 執行程式,設定能影響程式執行的引數和環境 ;

2 控制程式在指定的條件下停止執行;

3 當程式停止時,可以檢查程式的狀態;

4 當程式 crash 時,可以檢查 core 檔案;

5 可以修改程式的錯誤,並重新執行程式;

6 可以動態監視程式中變數的值;

7 可以單步執行程式碼,觀察程式的執行狀態。

gdb 程式除錯的物件是可執行檔案或者程序,而不是程式的原始碼檔案。然而,並不是所有的可執行檔案都可以用 gdb 除錯。如果要讓產生的可執行檔案可以用來除錯,需在執行 g++(gcc)指令編譯程式時,加上 -g 引數,指定程式在編譯時包含除錯資訊。除錯資訊包含程式裡的每個變數的型別和在可執行檔案裡的地址對映以及原始碼的行號。gdb 利用這些資訊使原始碼和機器碼相關聯。gdb 的基本命令較多,不做詳細介紹,大家如果需要進一步瞭解,請參見 gdb 手冊。

 #include <unistd.h> 
 #include <pthread.h> 
 #include <string.h> 

 pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; 
 pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; 
 pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER; 
 pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER; 

 static int sequence1 = 0; 
 static int sequence2 = 0; 

 int func1() 
 { 
    pthread_mutex_lock(&mutex1); 
    ++sequence1; 
    sleep(1); 
    pthread_mutex_lock(&mutex2); 
    ++sequence2; 
    pthread_mutex_unlock(&mutex2); 
    pthread_mutex_unlock(&mutex1); 

    return sequence1; 
 } 

 int func2() 
 { 
    pthread_mutex_lock(&mutex2); 
    ++sequence2; 
    sleep(1); 
    pthread_mutex_lock(&mutex1); 
    ++sequence1; 
    pthread_mutex_unlock(&mutex1); 
    pthread_mutex_unlock(&mutex2); 

    return sequence2; 
 } 

 void* thread1(void* arg) 
 { 
    while (1) 
    { 
        int iRetValue = func1(); 

        if (iRetValue == 100000) 
        { 
            pthread_exit(NULL); 
        } 
    } 
 } 

 void* thread2(void* arg) 
 { 
    while (1) 
    { 
        int iRetValue = func2(); 

        if (iRetValue == 100000) 
        { 
            pthread_exit(NULL); 
        } 
    } 
 } 

 void* thread3(void* arg) 
 { 
    while (1) 
    { 
        sleep(1); 
        char szBuf[128]; 
        memset(szBuf, 0, sizeof(szBuf)); 
        strcpy(szBuf, "thread3"); 
    } 
 } 

 void* thread4(void* arg) 
 { 
    while (1) 
    { 
        sleep(1); 
        char szBuf[128]; 
        memset(szBuf, 0, sizeof(szBuf)); 
        strcpy(szBuf, "thread3"); 
    } 
 } 

 int main() 
 { 
    pthread_t tid[4]; 
    if (pthread_create(&tid[0], NULL, &thread1, NULL) != 0) 
    { 
        _exit(1); 
    } 
    if (pthread_create(&tid[1], NULL, &thread2, NULL) != 0) 
    { 
        _exit(1); 
    } 
    if (pthread_create(&tid[2], NULL, &thread3, NULL) != 0) 
    { 
        _exit(1); 
    } 
    if (pthread_create(&tid[3], NULL, &thread4, NULL) != 0) 
    { 
        _exit(1); 
    } 

    sleep(5); 
    //pthread_cancel(tid[0]); 

    pthread_join(tid[0], NULL); 
    pthread_join(tid[1], NULL); 
    pthread_join(tid[2], NULL); 
    pthread_join(tid[3], NULL); 

    pthread_mutex_destroy(&mutex1); 
    pthread_mutex_destroy(&mutex2); 
    pthread_mutex_destroy(&mutex3); 
    pthread_mutex_destroy(&mutex4); 

    return 0; 
 }

執行編譯命令

g++ -g lock.cpp -o lock -lpthread
-g選項必須有

gdb除錯可執行檔案定位死鎖

a) gdb attach到目標程式

gdb attach 12814

b) 檢視該程序內的執行緒資訊
(gdb) info thread
  Id   Target Id         Frame 
* 1    Thread 0x7f77767ac700 (LWP 12814) "lock" 0x00007f777639998d in pthread_join (threadid=140151057311488, thread_return=0x0)
    at pthread_join.c:90
  2    Thread 0x7f7775fc6700 (LWP 12815) "lock" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
  3    Thread 0x7f77757c5700 (LWP 12816) "lock" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
  4    Thread 0x7f7774fc4700 (LWP 12817) "lock" 0x00007f777609330d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
  5    Thread 0x7f77747c3700 (LWP 12818) "lock" 0x00007f777609330d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
(gdb) 
c) 檢視當前執行緒棧空間
(gdb) bt
#0  0x00007f777639998d in pthread_join (threadid=140151057311488, thread_return=0x0) at pthread_join.c:90
#1  0x0000000000400b86 in main () at lock.cpp:110

可以知道主執行緒在等待子執行緒結束

c) 切換到執行緒2,並檢視其堆疊
(gdb) thread 2
[Switching to thread 2 (Thread 0x7f7775fc6700 (LWP 12815))]
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135     ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.
(gdb) bt
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007f777639adbd in __GI___pthread_mutex_lock (mutex=0x6020e0 <mutex2>) at ../nptl/pthread_mutex_lock.c:80
#2  0x0000000000400907 in func1 () at lock.cpp:18
#3  0x000000000040099f in thread1 (arg=0x0) at lock.cpp:43
#4  0x00007f77763986ba in start_thread (arg=0x7f7775fc6700) at pthread_create.c:333
#5  0x00007f77760ce41d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
(gdb) 

可以知道執行緒2在lock.cpp的第18行申請mutex2的互斥鎖。並阻塞在這兒了,我們進一步看看這個互斥鎖當前是被誰持有的。

(gdb) p mutex2
$1 = {__data = {__lock = 2, __count = 0, __owner = 12816, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, 
      __next = 0x0}}, __size = "\002\000\000\000\000\000\000\000\020\062\000\000\001", '\000' <repeats 26 times>, __align = 2}
(gdb) 

從這兒我們可以知道,mutex2這個鎖之前已經被執行緒12816(執行緒3)持有了,我們切換到這個執行緒去看看。

(gdb) thread 3
[Switching to thread 3 (Thread 0x7f77757c5700 (LWP 12816))]
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135     in ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S
(gdb) bt
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007f777639adbd in __GI___pthread_mutex_lock (mutex=0x6020a0 <mutex1>) at ../nptl/pthread_mutex_lock.c:80
#2  0x0000000000400963 in func2 () at lock.cpp:31
#3  0x00000000004009c6 in thread2 (arg=0x0) at lock.cpp:56
#4  0x00007f77763986ba in start_thread (arg=0x7f77757c5700) at pthread_create.c:333
#5  0x00007f77760ce41d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
(gdb) 

我們發現執行緒3在lock.cpp的第31行嘗試申請mutex1的互斥鎖,並陷入了阻塞。同理我們看看這個互斥鎖的ower是誰

(gdb) p mutex1
$2 = {__data = {__lock = 2, __count = 0, __owner = 12815, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, 
      __next = 0x0}}, __size = "\002\000\000\000\000\000\000\000\017\062\000\000\001", '\000' <repeats 26 times>, __align = 2}
(gdb) 

OK,mutex1這個互斥鎖被12815(執行緒2)持有了。

因此我們可以得到結論了,執行緒2在等待執行緒3釋放鎖資源,於此同時,執行緒3也在等待執行緒2釋放鎖資源,從而程序出現卡死現象。