死鎖發生需滿足四個必要條件(缺一不可):
典型交叉持鎖:Thread-1 鎖 A 等 B,Thread-2 鎖 B 等 A,雙方永久阻塞。
編譯含死鎖邏輯的 C++ 程式(-g 保留除錯符號):
g++ -g lock.cpp -o lock -lpthread
程式建立 4 條線程,其中 Thread-1/Thread-2 分別以相反順序獲取 mutex1/mutex2,必然觸發死鎖。
| 工具 | 用途 |
|---|---|
| pstack | 輸出進程所有線程的呼叫堆疊,快速定位阻塞點 |
| GDB | 深度除錯:檢查互斥鎖擁有者、切換線程上下文、查看原始碼行號 |
ps -ef | grep lock
# dyu 6721 5751 0 15:21 pts/3 00:00:00 ./lock
pstack 6721
關鍵觀察點:連續兩次輸出中,哪幾條線程堆疊完全不變且停留在 pthread_mutex_lock?
實驗中 Thread-4 (LWP 6722) 與 Thread-5 (LWP 6723) 兩次堆疊皆卡在 func1()/func2() 的 pthread_mutex_lock,判定為死鎖線程。
gdb attach 6721
(gdb) info threads
# 列出所有線程及當前函數
切換至疑似死鎖線程,查看呼叫堆疊:
(gdb) thread 5
(gdb) where
#3 0x0000000000400a9b in func1 () at lock.cpp:18
#4 0x0000000000400ad7 in thread1 (arg=0x0) at lock.cpp:43
檢查互斥鎖內部結構,確認擁有者:
(gdb) p mutex1
$1 = {__data = {__lock = 2, __owner = 6722, ...}} # 被 LWP 6722 擁有
(gdb) p mutex2
$3 = {__data = {__lock = 2, __owner = 6723, ...}} # 被 LWP 6723 擁有}
鐵證如山:Thread-4 (LWP 6723) 想鎖 mutex1(被 6722 擁有),Thread-5 (LWP 6722) 想鎖 mutex2(被 6723 擁有),典型交叉持鎖。
檢視 lock.cpp:18 與 :31 發現:
func1() 先鎖 mutex1 再鎖 mutex2func2() 先鎖 mutex2 再鎖 mutex1修正原則:全域統一鎖獲取順序(如按記憶體位址或自定義 ID 排序),打破循環等待條件。
tryLock(timeout) 替代無限等待pstack 快速定範圍,GDB 精準定位鎖擁有者與等待鏈,兩者結合可在幾分鐘內鎖定死鎖根因。掌握四大必要條件與排查套路,讓死鎖無處遁形。