死循环测试


程序员最痛苦的事莫过于深陷于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占用率问题也可以采用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值