[原]调试实战——程序CPU占用率飙升,你知道如何快速定位吗?
前言
如果我們自己的程序的CPU Usage(CPU占用率)飆升,并且居高不下,很有可能陷入了死循環。你知道怎么快速定位并解決嗎?今天跟大家分享幾種定位方法,希望對你有所幫助。
如何判斷是否有死循環?
通過電腦風扇的聲音猜測。
如果風扇一直響個不停,說明電腦很熱。高CPU占用率會導致CPU發熱量增大,從而導致風扇狂響。如果聽到風扇響個不停,可以打開任務管理器看看CPU占用率是不是很高。如果發現是我們的進程導致的高CPU占用率,那么可以進一步查看是不是有死循環。
通過CPU占用率來判斷。
對于多核CPU(尤其是性能強勁的CPU),一個核心的滿負荷運轉,并不會立刻導致CPU發熱量明顯增大,風扇可能不會有明顯響動。這時根據風扇聲音不能輕易判斷出是否有死循環,但是我們可以通過CPU占用率來判斷。
如果CPU是單核的,那么當CPU處于滿負荷運轉狀態,CPU占用率會接近100%。如果CPU是4核的,并且這4個核心都處于滿負荷運轉狀態,那么CPU占用率會接近100%,如果只有一個核心是滿負荷運轉狀態,那么CPU占用率會在25%(100 / 4 = 25)左右。如果我們發現某個進程的CPU占用率居高不下,有可能是死循環了。
注意: 很多死循環都是busy類型的,如果是idle類型的死循環,上面的方法不適用。
下面介紹幾個我經常使用的工具,可以比較便捷的排查此類問題。
1. process explorer
在前面的文章里跟大家介紹過,使用process explorer可以查看線程的調用棧及CPU占用率。如果程序里的某個功能遲遲不能完成,我的第一反應是,按Ctrl + Shift + Esc打開任務管理器(我已經使用process explorer替換了系統自帶的任務管理器,所以啟動的是process explorer。如何使用process explorer替換系統自帶的任務管理器,請參考文章[原]排錯實戰——使用process explorer替換任務管理器)。
啟動process explorer后,雙擊我們關心的進程,切換到Thread頁,在這里我們可以看到當前進程中的所有線程。雙擊某個線程就可以查看調用棧,在彈出的調用棧界面,點擊左下角的Refresh按鈕可以刷新。
如果每次刷新都能看到某個函數,很有可能是在這個函數中出現了死循環。對照源碼,也許能直接能看出原因。
使用process explorer注意: 需要正確加載調試符號才可以看到對應的函數名。
2. windbg
如果不能使用process explorer定位到具體的原因,可以使用windbg附加到進程中進行更深入的調查。我們需要找出哪個線程運行的時間最長,因為一般死循環的線程占用的CPU時間會比較長。應該怎么找呢?????
使用.ttime命令
.ttime可以查看當前線程的運行時間(用戶態運行時間和內核態運行時間)。但是.ttime有個不足之處——沒有輸出相關的線程標識。我們需要根據其它信息來獲取當前線程的標識。
如果想查看所有線程的運行時間怎么辦呢?當然可以手動切換到另外一個線程,然后執行.ttime。如果線程數量很多的話,這可是個體力活。不要怕,我們可以通過命令~*e .ttime來獲取每個線程的運行時間。因為.ttime輸出結果中沒有線程標識,我們需要執行命令 ~*e ? $tid;.ttime 把對應的線程ID一起輸出。
獲取所有線程ID和運行時間簡單向大家解釋下這條命令:
~*e會遍歷所有線程并執行后面跟著的命令。其實,~*就可以遍歷所有線程,比如我們在前面的文章里用到的~* kvn命令來查看所有線程的調用棧。但是對于某些命令,如果不加e,windbg可能不能正確解析,會報錯。
? $tid評估表達式$tid的值,?在windbg中表示Evaluate的意思,會評估后面表達式的值。$tid是偽變量,代表了當前線程的線程ID。
; 分號是命令分割符。
.ttime查看當前線程的運行時間。
整條命令的效果是:遍歷每個線程,輸出其對應的線程ID和運行時間。
如果覺得上面的命令太長了,還可以使用更簡單的命令!runaway查看線程運行時間。
下面是我用!runaway命令排查高CPU占用率的屏幕錄像。
3. visual studio
如果是正在開發的程序在運行過程中出現了死循環,我會考慮用vs來附加到進程(如果進程是通過Ctrl + F5啟動的話,并沒有被調試)。然后通過Parallel Stacks查看所有線程,并用肉眼查找可能出問題的線程。因為我不知道vs中是否有類似!runaway的命令。如果哪位小伙伴有更好的辦法,請一定要留言告訴我!
下面是我用Parallel Stacks功能排查高CPU占用率的屏幕錄像。
小提示: 按Ctrl + Alt + p可以快速打開附加進程界面。
小結
以上三種工具,我會先使用process explorer大體定位下問題,因為可以非常方便的通過Ctrl + Shift + Esc啟動。如果用process explorer解決不了,我會根據情況使用windbg或者vs。如果vs正開著(通常是正在寫代碼的時候),就順手用vs附加到對應的進程中。如果vs沒開著,當然會使用windbg進行排查了。????
實戰代碼
如果你想動手實戰,復制下面的代碼到工程里就可以實戰了。
簡單介紹下代碼:
示例代碼中啟動了8個線程,是為了增大排查的難度,只有一個線程的情況太簡單了。
函數FindFirstRepeatElementIndex()的用途是找到給定的數據中第一次出現重復的數據的索引。
除了我們發現的死循環的問題,還有什么地方可以優化呢?命名,效率,各個方面都可以優化哦,歡迎留言交流。
總結
使用process explorer的線程相關功能,在某些情況下,我們甚至可以不用調試器,對照源碼就可以找出問題所在。
visual studio的并行調用棧可以讓我們一次性看到所有線程的調用棧,很是方便。不像Call Stack,每次只能查看一個線程的調用棧。當然除了看所有線程的調用棧,還有更多用途等待大家挖掘。
一般,如果一個線程的運行時間遠大于其它線程,這個線程很有可能是與死循環相關的線程。
windbg的!runaway命令可以查看每個線程運行的時間,運行時間最長的線程會排在第一位。
~*e ? $tid;.ttime可以查看所有線程的運行時間。
~Ns切換到第N號線程。
~~[TID]s 切換到TID對應的線程。
參考資料
《格蠹匯編》
《Windows Sysinternals 實戰指南》
猜你喜歡:
[原]調試實戰——使用windbg調試崩潰在ole32!CStdMarshal::DisconnectSrvIPIDs
[原]調試實戰——崩潰在ComFriendlyWaitMtaThreadProc
[原]調試實戰——調試PInvoke導致的內存破壞
[原]調試實戰——調試excel啟動時死鎖
[原]調試實戰——調試TerminateThread導致的死鎖
[原]調試實戰——調試DLL卸載時的死鎖
[原]排錯實戰——拯救加載調試符號失敗的IDA
[原]排錯實戰——使用process explorer替換任務管理器
[原]排錯實戰——VS清空最近打開的工程記錄
[原]排錯實戰——解決Tekla通過.tsep安裝插件失敗的問題
[原]排錯實戰——你知道拖動窗口時只顯示虛框怎么設置嗎?
[原]排錯實戰——通過對比分析sysinternals事件修復程序功能異常
你知道怎么使用DebugView查看調試信息嗎?
歡迎留言交流
總結
以上是生活随笔為你收集整理的[原]调试实战——程序CPU占用率飙升,你知道如何快速定位吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用DbContextPool提高EfC
- 下一篇: ASP.NET Core基于K8S的微服