如何檢查線程是否死鎖了?
產生死鎖的四個必要條件
(1) 互斥條件:一個資源每次只能被一個進程(線程)使用。
(2) 請求與保持條件:一個進程(線程)因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件 : 此進程(線程)已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件 : 多個進程(線程)之間形成一種頭尾相接的循環等待資源關系。
可以使用 jstack或者pstack 和 gdb 工具對死鎖程序進行分析。
pstack: 功能是打印輸出此進程的堆棧信息。可以輸出所有線程的調用關系棧
jstack:jstack是java虛擬機自帶的一種堆棧跟蹤工具,所以僅適用於java程序,功能跟pstack一樣,但是更強大,可以提示哪個地方可能死鎖了。
pstack和jstack判斷死鎖,都需要多執行幾次命令,觀察每次的輸出結果,才能推測是否死鎖了。
gdb:
1 運行程序,設置能影響程序運行的參數和環境 ;
2 控制程序在指定的條件下停止運行;
3 當程序停止時,可以檢查程序的狀態;
4 當程序 crash 時,可以檢查 core 文件;
5 可以修改程序的錯誤,並重新運行程序;
6 可以動態監視程序中變量的值;
7 可以單步執行代碼,觀察程序的運行狀態。
線程死鎖分析:
1. 連續多次執行 $pstack <PID> 其中PID是進程號
查看每個線程的函數調用關系的堆棧,觀察每個線程當前的執行點是否在等待一個鎖。
多次執行該命令,發現某些線程的當前執行點不變,總是在等待同一個鎖,就可以懷疑是否死鎖了。
如果懷疑哪些線程發生死鎖了,可以采用gdb 進一步attach線程並進行分析。
2. 執行$gdb -p <PID> 或者 $gdb attach <PID>
(gdb) info thread
(gdb) thread <thread ID>
BTW:gdb的all-stop和non-stop模式 (GDBv7.0及之上版本支持)
默認都是all-stop,即在某線程的代碼裏設置了斷點,觸發斷點時所有的線程都會中斷。
non-stop是多線程調試的好幫手,某個線程裏的斷點觸發了,其它線程還照常run,可以重現死鎖的場景。
想打開non-stop,可以修改~/.gdbinit,添加如下三行:
set target-async 1
set pagination off
set non-stop on
或者在進入gdb以後,依次執行以上三行,可以臨時開啟non-stop,退出gdb後失效。(猜是這樣,需要實驗驗證)
另外,如果多個線程走的一個代碼塊,停在了同一個斷點上,例如只想讓線程1和線程3繼續,使用:
(gdb) thread apply 3 1 continue
再BTW,判斷熱鎖。熱鎖是指經常被打開關閉打開關閉的鎖,由於多個線程對鎖或者臨界區的競爭造成的。
* 頻繁的線程的上下文切換:從操作系統對線程的調度來看,當 線程在等待資源而阻塞的時候,操作系統會將之切換出來,放到等待的隊列,當線程獲得資源之後,調度算法會將這個線程切換進去,放到執行隊列中。
* 大量的系統調用:因為線程的上下文切換,以及熱鎖的競爭,或 者臨界區的頻繁的進出,都可能導致大量的系統調用。
* 大部分 CPU開銷用在 “系統態 ”:線程上下文切換,和系統調用,都會導致 CPU在 “系統態 ”運行,換而言之,雖然系統很忙碌,但是 CPU用在 “用戶態 ”的比例較小,應用程序得不到充分的 CPU資源。
* 隨著 CPU數目的增多,系統的性能反而下降。因為 CPU數目多,同 時運行的線程就越多,可能就會造成更頻繁的線程上下文切換和系統態的 CPU開銷,從而導致更糟糕的性能。
可以通過腳本來固定時間間隔調用pstack,和查看系統資源使用情況的命令,比如top,分別將輸出打印到log文件裏。
當然要每次執行命令都得打印時間戳。
分析log文件,可以大概推測出熱鎖所在的位置。可以通過優化熱鎖,來提升程序的性能,例如吞吐量、響應時間。
如何檢查線程是否死鎖了?