死循環測試


程序員最痛苦的事莫過於深陷於BUG的泥潭,我也沒少在這上面摔跤。這裏,我把自己的一些經驗教訓總結出來,涉及的內容包括死循環、死鎖、內存泄漏以及內存訪問錯誤等,如果能對朋友們有所幫助,那就再好不過了。不過,我不打算按照循序漸進的方式來撰寫這些文章,而是想到哪寫到哪,也許到最後纔會形成一個完整的系列。

不管是單線程還是多線程程序,死循環都算是相對比較容易解決的,但也有一些技巧在裏面,本節就將對這個問題作以簡單的總結。

如何判斷死循環

死循環是比較容易觀察出來的,其表象主要如下:

1. CPU佔用一直居高不下,從任務管理器看基本上一直處於100%狀態。

2. 程序長時間運行始終未能進入預期狀態。

另外,我們還可以從函數調用棧(Call Stack)和程序輸出的調試信息來判斷程序是否進入了死循環。

簡單程序的死循環調試方法

這裏所謂的簡單程序主要指單線程程序,其調試方法也很簡單,只需要單步跟蹤就行。對於VC6來說,在程序入口處設置一斷點(快捷鍵爲F9),然後F5調試運行,接着再使用F10或者F11單步跟蹤,你很容易定位發生死循環的地方。這些調試命令以及快捷鍵在Debug菜單下都有,我們只需熟悉它即可,其它調試器比如.NET的使用方法也和此類似。

爲了加快調試速度,我們可以先在自己認爲可能出現死循環地方的前後位置加上斷點,如果不能確定,那麼就在程序執行路徑上依次設置幾個斷點,然後F5運行。當程序到斷點停下來後再按F5,直到不能繼續前進爲止,那麼說明死循環出現在上一個斷點之後,記下其位置。接下來,去掉之前的所有斷點,再次調試運行,當執行到前面那個斷點後,再單步跟蹤。這是一種快速分段定位方法,適合於很多問題的調試。

複雜程序的死循環調試方法

這裏所謂的複雜程序主要指大型的多線程程序,死循環的發生往往是在程序運行過程中,有些情況還不是每次都發生,而是隻在某種特定條件下才出現。遇到這種情況,我們就應抓住機會,力爭一出現問題就將其抓住,爲此,首先要搭好測試環境,最好能夠調試運行程序,否則出現了問題也只能束手無策。

出現了死循環又如何定位呢?多線程程序有多條執行路徑,不像單線程程序那樣可以一次單步跟蹤到底,因此我們的首要任務是判斷是哪個線程出現了死循環。

爲此,我們需要藉助外部工具。這個工具不用到處找,Windows就有自帶。選擇“控制面板/管理工具”中的“性能”工具,如下圖:

沒有用過該工具的朋友可以先自行熟悉一下。在右側窗口中單擊右鍵,選擇“添加計數器”,或者直接單擊工具欄中的“+”按鈕,將會彈出一個設置窗口,其中的“性能對象”下拉菜單中給出了性能計數器的分類,左側列表框中則給出了相應的性能計數器,右側列表框則是監視對象。

爲了更好的講解調試過程,我們將以一個具體程序爲例。先使用VC6嚮導創建一個名爲InfiniteLoop的控制檯程序,其代碼如下:

#include <windows.h>
#include <process.h>
#include <stdio.h>

int g_loop = 1; // 循環控制標誌

unsigned __stdcall test_thread(void* ) // 測試線程
{
    unsigned int count = 0; // 循環計數
    while(g_loop)
    {
        //printf("loop: %d/n", count); // 該條語句將影響CPU佔用率
        ++count;
    }
    return 0;
}

int main(int argc, char* argv[])
{
    HANDLE hThread = NULL;
    unsigned dwThreadId = 0;
    printf("Press any key to start infinite loop, and do that again to stop it./n");
    system("pause"); // 暫停主線程,等待按鍵開始無限循環
    hThread = (HANDLE)_beginthreadex(NULL, 0, test_thread, NULL, 0, &dwThreadId); // 創建測試線程
    if(hThread == NULL) // 創建線程失敗
    {
        printf("Create thread failed!/n");
        return -1;
    }
    system("pause"); // 暫停主線程,等待按鍵結束無限循環
    g_loop = 0;
    WaitForSingleObject(hThread, INFINITE); // 等待測試線程結束
    CloseHandle(hThread); // 關閉線程句柄
    printf("Program exits now./n");
    return 0;
}

這是一個最簡單的多線程程序,主線程(即main函數)等待用戶按任意鍵創建一個測試線程,然後再次等待按任意鍵退出整個程序,而測試線程則是一個無限循環,它將使CPU的佔用率爲100%。

我們先打開前面的性能分析工具,然後F5調試運行程序。這裏,我們需要知道的是目標程序每個線程的CPU佔用情況,因此添加計數器時,性能對象選擇“Thread”,計數器選擇“% Processor Time”,監視對象選擇調試程序的兩個線程:“InfiniteLoop/0”和“InfiniteLoop/1”,注意這裏的數字0和1只是線程編號,跟線程ID和句柄沒有關係。見下圖:

添加計數器後,我們將看到顯示窗口中動態的描繪出一幅曲線圖(見下圖)。雙擊位置最高的那條曲線,下面計數器列表中的高亮條自動定位到了實例1上,這樣我們便確認了佔用CPU最高的是1號線程。

接下來,我們需要得到1號線程的ThreadID,爲此需要再添加一個計數器。這次,我們只選擇1號線程實例,計數器選ID Thread,如下圖:

添加完ID Thread計數器後,選擇“ID Thread 1”實例,可以看到其值爲1444,這便是ThreadID,如下圖。需說明的是圖中位置顯示的值對於不同的計數器對象具有不同的含義,請參考相關幫助文檔。

獲得了線程ID,就可以暫停程序了,選擇Debug菜單下的Break指令即可。然後再選擇Debug菜單下的Threads調出線程窗口,如下:

線程窗口顯示了程序的所有線程,但其ThreadID是以十六進制方式顯示的。沒關係,使用Windows自帶的計算器工具(運行中輸入calc即可),將十進制的1444轉換爲十六進制,結果爲5a4。於是在線程窗口中選中該項,單擊“OK”,VC6調試器將切換到該線程的執行空間,如下圖:

到此爲止,我們就可以按照前面處理單線程的方式進行調試了。

總結

本節內容表面是講述死循環的調試技巧,實則介紹了Windows性能工具以及VC6線程窗口的使用(.NET的線程窗口使用起來更方便),有心的朋友一定能從性能工具中找多更多對自己有價值的功能。

此外,這裏的線程CPU佔用率分析方法不只適用於死循環,其它異常的高CPU佔用率問題也可以採用。

發佈了0 篇原創文章 · 獲贊 7 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章