簡(jiǎn)介
死鎖 (deallocks):
是指兩個(gè)或兩個(gè)以上的進(jìn)程(線程)在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,,若無(wú)外力作用,,它們都將無(wú)法推進(jìn)下去。此時(shí)稱(chēng)系統(tǒng)處于死鎖狀態(tài)或
系統(tǒng)產(chǎn)生了死鎖,,這些永遠(yuǎn)在互相等待的進(jìn)程(線程)稱(chēng)為死鎖進(jìn)程(線程),。
由于資源占用是互斥的,當(dāng)某個(gè)進(jìn)程提出申請(qǐng)資源后,,使得有關(guān)進(jìn)程(線程)在無(wú)外力協(xié)助下,,永遠(yuǎn)分配不到必需的資源而無(wú)法繼續(xù)運(yùn)行,,這就產(chǎn)生了一種特殊現(xiàn)象
死鎖。
一種交叉持鎖死鎖的情形,,此時(shí)執(zhí)行程序中兩個(gè)或多個(gè)線程發(fā)生永久堵塞(等待),,每個(gè)線程都在等待被其它線程占用并堵塞了的資源。例如,,如果線
程 1 鎖住了記錄 A 并等待記錄 B,,而線程 2 鎖住了記錄 B 并等待記錄 A,這樣兩個(gè)線程就發(fā)生了死鎖現(xiàn)象,。在計(jì)算機(jī)系統(tǒng)中 ,
如果系統(tǒng)的資源分配策略不當(dāng),,更常見(jiàn)的可能是程序員寫(xiě)的程序有錯(cuò)誤等,則會(huì)導(dǎo)致進(jìn)程因競(jìng)爭(zhēng)資源不當(dāng)而產(chǎn)生死鎖的現(xiàn)象,。
回頁(yè)首 產(chǎn)生死鎖的四個(gè)必要條件
(1) 互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程(線程)使用,。 (2) 請(qǐng)求與保持條件:一個(gè)進(jìn)程(線程)因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放,。 (3) 不剝奪條件 : 此進(jìn)程(線程)已獲得的資源,,在末使用完之前,不能強(qiáng)行剝奪,。 (4) 循環(huán)等待條件 : 多個(gè)進(jìn)程(線程)之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
圖 1. 交叉持鎖的死鎖示意圖:
注釋:在執(zhí)行 func2 和 func4 之后,,子線程 1 獲得了鎖 A,,正試圖獲得鎖
B,但是子線程 2 此時(shí)獲得了鎖 B,,正試圖獲得鎖 A,,所以子線程 1 和子線程 2 將沒(méi)有辦法得到鎖 A 和鎖
B,因?yàn)樗鼈兏髯员粚?duì)方占有,,永遠(yuǎn)不會(huì)釋放,,所以發(fā)生了死鎖的現(xiàn)象。
回頁(yè)首 使用 pstack 和 gdb 工具對(duì)死鎖程序進(jìn)行分析
pstack 在 Linux 平臺(tái)上的簡(jiǎn)單介紹
pstack 是 Linux(比如 Red Hat Linux 系統(tǒng),、Ubuntu Linux 系統(tǒng)等)下一個(gè)很有用的工具,,它的功能是打印輸出此進(jìn)程的堆棧信息??梢暂敵鏊芯€程的調(diào)用關(guān)系棧,。
gdb 在 Linux 平臺(tái)上的簡(jiǎn)單介紹
GDB 是 GNU 開(kāi)源組織發(fā)布的一個(gè)強(qiáng)大的 UNIX 下的程序調(diào)試工具。Linux 系統(tǒng)中包含了 GNU 調(diào)試程序 gdb,,它是一個(gè)用來(lái)調(diào)試 C 和 C++ 程序的調(diào)試器,。可以使程序開(kāi)發(fā)者在程序運(yùn)行時(shí)觀察程序的內(nèi)部結(jié)構(gòu)和內(nèi)存的使用情況 .
gdb 所提供的一些主要功能如下所示:
1 運(yùn)行程序,,設(shè)置能影響程序運(yùn)行的參數(shù)和環(huán)境 ;
2 控制程序在指定的條件下停止運(yùn)行,;
3 當(dāng)程序停止時(shí),,可以檢查程序的狀態(tài);
4 當(dāng)程序 crash 時(shí),,可以檢查 core 文件,;
5 可以修改程序的錯(cuò)誤,并重新運(yùn)行程序,;
6 可以動(dòng)態(tài)監(jiān)視程序中變量的值,;
7 可以單步執(zhí)行代碼,觀察程序的運(yùn)行狀態(tài),。
gdb 程序調(diào)試的對(duì)象是可執(zhí)行文件或者進(jìn)程,,而不是程序的源代碼文件。然而,,并不是所有的可執(zhí)行文件都可以用 gdb
調(diào)試,。如果要讓產(chǎn)生的可執(zhí)行文件可以用來(lái)調(diào)試,需在執(zhí)行 g++(gcc)指令編譯程序時(shí),,加上 -g
參數(shù),,指定程序在編譯時(shí)包含調(diào)試信息。調(diào)試信息包含程序里的每個(gè)變量的類(lèi)型和在可執(zhí)行文件里的地址映射以及源代碼的行號(hào),。gdb
利用這些信息使源代碼和機(jī)器碼相關(guān)聯(lián),。gdb 的基本命令較多,不做詳細(xì)介紹,,大家如果需要進(jìn)一步了解,,請(qǐng)參見(jiàn) gdb 手冊(cè)。
清單 1. 測(cè)試程序
#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;
}
|
清單 2. 編譯測(cè)試程序
[dyu@xilinuxbldsrv purify]$ g++ -g lock.cpp -o lock -lpthread
|
清單 3. 查找測(cè)試程序的進(jìn)程號(hào)
[dyu@xilinuxbldsrv purify]$ ps -ef|grep lock
dyu 6721 5751 0 15:21 pts/3 00:00:00 ./lock
|
清單 4. 對(duì)死鎖進(jìn)程第一次執(zhí)行 pstack(pstack –進(jìn)程號(hào))的輸出結(jié)果
[dyu@xilinuxbldsrv purify]$ pstack 6721
Thread 5 (Thread 0x41e37940 (LWP 6722)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a9b in func1() ()
#4 0x0000000000400ad7 in thread1(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 4 (Thread 0x42838940 (LWP 6723)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a17 in func2() ()
#4 0x0000000000400a53 in thread2(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x43239940 (LWP 6724)):
#0 0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6
#1 0x0000003d19c9a364 in sleep () from /lib64/libc.so.6
#2 0x00000000004009bc in thread3(void*) ()
#3 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#4 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x43c3a940 (LWP 6725)):
#0 0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6
#1 0x0000003d19c9a364 in sleep () from /lib64/libc.so.6
#2 0x0000000000400976 in thread4(void*) ()
#3 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#4 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2b984ecabd90 (LWP 6721)):
#0 0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0
#1 0x0000000000400900 in main ()
|
清單 5. 對(duì)死鎖進(jìn)程第二次執(zhí)行 pstack(pstack –進(jìn)程號(hào))的輸出結(jié)果
[dyu@xilinuxbldsrv purify]$ pstack 6721
Thread 5 (Thread 0x40bd6940 (LWP 6722)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a87 in func1() ()
#4 0x0000000000400ac3 in thread1(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 4 (Thread 0x415d7940 (LWP 6723)):
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a03 in func2() ()
#4 0x0000000000400a3f in thread2(void*) ()
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x41fd8940 (LWP 6724)):
#0 0x0000003d19c7aec2 in memset () from /lib64/libc.so.6
#1 0x00000000004009be in thread3(void*) ()
#2 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#3 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x429d9940 (LWP 6725)):
#0 0x0000003d19c7ae0d in memset () from /lib64/libc.so.6
#1 0x0000000000400982 in thread4(void*) ()
#2 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#3 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2af906fd9d90 (LWP 6721)):
#0 0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0
#1 0x0000000000400900 in main ()
|
連續(xù)多次查看這個(gè)進(jìn)程的函數(shù)調(diào)用關(guān)系堆棧進(jìn)行分析:當(dāng)進(jìn)程吊死時(shí),多次使用 pstack
查看進(jìn)程的函數(shù)調(diào)用堆棧,,死鎖線程將一直處于等鎖的狀態(tài),,對(duì)比多次的函數(shù)調(diào)用堆棧輸出結(jié)果,確定哪兩個(gè)線程(或者幾個(gè)線程)一直沒(méi)有變化且一直處于等鎖的
狀態(tài)(可能存在兩個(gè)線程 一直沒(méi)有變化),。
輸出分析:
根據(jù)上面的輸出對(duì)比可以發(fā)現(xiàn),,線程 1 和線程 2 由第一次 pstack 輸出的處在 sleep 函數(shù)變化為第二次 pstack
輸出的處在 memset 函數(shù)。但是線程 4 和線程 5 一直處在等鎖狀態(tài)(pthread_mutex_lock),,在連續(xù)兩次的 pstack
信息輸出中沒(méi)有變化,,所以我們可以推測(cè)線程 4 和線程 5 發(fā)生了死鎖。
Gdb into thread 輸出:
清單 6. 然后通過(guò) gdb attach 到死鎖進(jìn)程
(gdb) info thread
5 Thread 0x41e37940 (LWP 6722) 0x0000003d1a80d4c4 in __lll_lock_wait ()
from /lib64/libpthread.so.0
4 Thread 0x42838940 (LWP 6723) 0x0000003d1a80d4c4 in __lll_lock_wait ()
from /lib64/libpthread.so.0
3 Thread 0x43239940 (LWP 6724) 0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
2 Thread 0x43c3a940 (LWP 6725) 0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
* 1 Thread 0x2b984ecabd90 (LWP 6721) 0x0000003d1a807b35 in pthread_join ()
from /lib64/libpthread.so.0
|
清單 7. 切換到線程 5 的輸出
(gdb) thread 5
[Switching to thread 5 (Thread 0x41e37940 (LWP 6722))]#0 0x0000003d1a80d4c4 in
__lll_lock_wait () from /lib64/libpthread.so.0
(gdb) where
#0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2 0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x0000000000400a9b in func1 () at lock.cpp:18
#4 0x0000000000400ad7 in thread1 (arg=0x0) at lock.cpp:43
#5 0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6 0x0000003d19cd40cd in clone () from /lib64/libc.so.6
|
清單 8. 線程 4 和線程 5 的輸出
(gdb) f 3
#3 0x0000000000400a9b in func1 () at lock.cpp:18
18 pthread_mutex_lock(&mutex2);
(gdb) thread 4
[Switching to thread 4 (Thread 0x42838940 (LWP 6723))]#0 0x0000003d1a80d4c4 in
__lll_lock_wait () from /lib64/libpthread.so.0
(gdb) f 3
#3 0x0000000000400a17 in func2 () at lock.cpp:31
31 pthread_mutex_lock(&mutex1);
(gdb) p mutex1
$1 = {__data = {__lock = 2, __count = 0, __owner = 6722, __nusers = 1, __kind = 0,
__spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = "\002\000\000\000\000\000\000\000B\032\000\000\001", '\000'
<repeats 26 times>, __align = 2}
(gdb) p mutex3
$2 = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = '\000' <repeats 39 times>, __align = 0}
(gdb) p mutex2
$3 = {__data = {__lock = 2, __count = 0, __owner = 6723, __nusers = 1,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = "\002\000\000\000\000\000\000\000C\032\000\000\001", '\000'
<repeats 26 times>, __align = 2}
(gdb)
|
從上面可以發(fā)現(xiàn),線程 4 正試圖獲得鎖 mutex1,,但是鎖 mutex1 已經(jīng)被 LWP 為 6722 的線程得到(__owner
= 6722),,線程 5 正試圖獲得鎖 mutex2,但是鎖 mutex2 已經(jīng)被 LWP 為 6723 的 得到(__owner =
6723),,從 pstack 的輸出可以發(fā)現(xiàn),,LWP 6722 與線程 5 是對(duì)應(yīng)的,LWP 6723 與線程 4 是對(duì)應(yīng)的,。所以我們可以得出,,
線程 4 和線程 5 發(fā)生了交叉持鎖的死鎖現(xiàn)象。查看線程的源代碼發(fā)現(xiàn),,線程 4 和線程 5 同時(shí)使用 mutex1 和
mutex2,,且申請(qǐng)順序不合理。
回頁(yè)首 總結(jié)
本文簡(jiǎn)單介紹了一種在 Linux
平臺(tái)下分析死鎖問(wèn)題的方法,,對(duì)一些死鎖問(wèn)題的分析有一定作用,。希望對(duì)大家有幫助。理解了死鎖的原因,,尤其是產(chǎn)生死鎖的四個(gè)必要條件,,就可以最大可能地避
免、預(yù)防和解除死鎖,。所以,,在系統(tǒng)設(shè)計(jì)、進(jìn)程調(diào)度等方面注意如何不讓這四個(gè)必要條件成立,,如何確定資源的合理分配算法,避免進(jìn)程永久占據(jù)系統(tǒng)資源,。此外,,
也要防止進(jìn)程在處于等待狀態(tài)的情況下占用資源 ,
在系統(tǒng)運(yùn)行過(guò)程中,對(duì)進(jìn)程發(fā)出的每一個(gè)系統(tǒng)能夠滿(mǎn)足的資源申請(qǐng)進(jìn)行動(dòng)態(tài)檢查,,并根據(jù)檢查結(jié)果決定是否分配資源,,若分配后系統(tǒng)可能發(fā)生死鎖,則不予分配,,否
則予以分配,。因此,對(duì)資源的分配要給予合理的規(guī)劃,,使用有序資源分配法和銀行家算法等是避免死鎖的有效方法,。
|