微信号:cpp_coder

介绍:最专业的C/C++编程学习和程序员发展的综合平台.提供C/C++、汇编语言等、STL、MFC、QT等等学习、数据结构算法思维分析、各领域项目经验分享、资源下载、招聘和资讯的综合服务.

微软亚洲研究院的笔试题:利用程序控制在任务管理器中实现不同的曲线图

2016-06-18 22:14 cpp_coder

是微软亚洲研究院的笔试题,利用程序控制在任务管理器中实现不同的曲线图。我这里只实现了方波的走势,曲线走势没有办法支持。同时不能支持多处理器同时显示曲线,不同的CPU执行效果可能有差异,不过可以给你提供一个很好的基础,你只需要继续完善,相信是可以做出来的。

文章来源:C++技术网 原创文章版权所有,未经授权,禁止转载。

    今天下午研究了一下控制任务管理器中的CPU利用率走势图的波形曲线,有一点收获,总结一下。虽然距离最终的效果还有点差距,但是也学习到不少东西,而这些东西都是通过实践观察总结所得,所以花的时间比较多。

    先来看看效果图:

【逼近直线的走势图】


【等距离尖波走势图】


【等距离方波走势图】


【不等距尖波走势图】


    从以上效果图可以看到,四个走势图基本上是得到了,不过不能同时在四个处理器上显示出来,这也就是我这个方法的局限性,不过对于单个或者两个处理器的运 行,还是可以得到一定的效果,最佳的效果就是单个处理器实现一种效果。而且,可能在不同的CPU上执行的效果有些差异,可能原始代码执行得到的并不是期望 中的效果,需要你手动调整一下。
    作为基础版本,先研究到这里,这里的不足之处和局限性我都会告诉大家原因,有兴趣的话,你可以在此基础上研究,做出更好的效果,我会给出一些思路。
    我的程序写的时候是多线程的,但是多线程分别在4个处理器上不能同时得到对应的效果图,所以本文就只用单线程来演示了每一个效果。不过四种效果的代码都是一样的,传入函数的参数不一样,就可以实现不同的效果图。
    任务管理器中CPU使用率的原理也就是CPU单位时间内运行的时间的比例,运行时间我们不好获取,但是我们可以方便获取空闲时间。如果通过空闲时间来进行 走势控制,效果会很好。不过,我这里并不是用这种方法。利用空闲时间的实现方法,你可以自己研究一下。我下面说说我用的方法。
    为了得到需要的曲线,我们需要让本来利用率不高的CPU忙碌起来,这样走势图才会升起来。如何让CPU忙碌起来呢?最简单的就是循环了。如果执行大量的空循环,CPU的利用率马上就100%了,要让利用率维持,就要持续的循环。
    然而我们要得到的不是一直持续的100%,而是0-100%之间的曲线变化,所以持续空循环也是不行的,我们得加以控制。控制的办法,就是让线程暂停执行,这样没有大量循环的执行,CPU利用率就恢复原始的水平,很低的利用率的水平了。
    那如何控制CPU的利用率维持在0-100%之间的一个水平上呢,比如50%。利用率的统计也是在单位时间内统计得到的,我们只要在单位时间内进行大量循 环和暂停线程的执行的控制的话,就可以控制CPU利用率在一个水平上。不过,这个你需要自己调整循环的次数和随后的线程暂停执行的时间,来达到一定的稳定 的利用率。这个会因CPU而异,这也是我为什么开始说我给出的代码在你电脑可能跑不出预期的曲线的原因。不同的CPU对循环的执行,以及不同编译器的处理 生成的指令都不一样,会对最终的结果产生影响,所以你需要自己测试,得到一个比较满意的结果。
    通过测试,基本的原理是:循环次数越多,CPU的利用率也就持续上升的越多,当达到一定次数后,使用率达到100%。我们要将利用率控制在一定范围内,就 要在利用率上升到这个水平时进行拦截,也就是暂停线程执行循环,将利用率降下来,等暂停时间一到,线程有继续执行,这样利用率又提高了。也就是这个动态的 降低和提高,才将利用率维持在一个水平的。所以,循环的次数和紧接着的暂停执行的时间长度就是两个非常关键的要素。表现在曲线上,暂停时间越长,局部的波 动越大,也就是整体上是一个直线的走势,局部跳跃比较大。
    那么曲线的高度的控制也就说清楚了。不过,你应该从这个原理得到,我们这里只能产生直线走势,除了利用率切换时的斜线外,其他的都是基本直线,无法产生曲线形状。这也是我们这个方法的局限性之一。
    那么控制利用率水平线的代码如下:

for (int i=0;i<sum;i++);//控制总体的高度 
Sleep(2);//控制局部振幅

    从代码可以看到,循环控制的是总体的高度,线程睡眠时间则是控制局部振幅的。暂停执行线程也就让CPU的空闲时间增多了,也就降低了利用率了。
    那么下一个问题就是,电脑的CPU是四核的,我们要确定一个CPU处理器去执行,才好看到我们的效果,否则不指定一个具体的CPU处理器,那么执行将会是分散的,可能会在4个处理器之间来回切换,这样就无法得到需要的走势曲线了。
    我们使用CreateThread创建一个线程。为了让其他线程对我们制造曲线的线程的干扰,我们将我们的线程的优先级设置为最高,这样我们的线程可以得到大量的CPU执行时间,就可以让CPU尽可能反映出我们线程产生的效果。设置线程优先级的API函数为:

SetThreadPriority(线程句柄,0-31);

    参数的意义在括号中给出了,第一个是线程的句柄,也就是CreateThread返回的值,第二个参数为优先等级,取值为0-31,数值越大等级越高,也就越优先被执行。不过,你需要知道的是,线程只是进程的一个执行体而已。我们给线程执行的优先级只是相对于进程的优先级而言的,不是绝对的优先级。而我们要使用CPU则需要和其他进程的线程进行对抗,所以,我们需要将我们的进程优先级设置为最高的。这样就进一步让线程的优先级更高。设置进程优先级的API函数为:

SetPriorityClass(GetCurrentProcess(), 31);

    获取本进程的句柄,可以使用GetCurrentProcess()。需要注意的是,要最新设置进程的优先级,然后再设置进程内的线程的优先级,也就是先确定一个大方向,然后再确定小方向。
    那么到此,优先级就搞定了。另一个问题就是要将线程指定到某一个CPU执行单元去执行。我们可以使用API函数SetThreadAffinityMask,第一个参数为线程句柄,第二个参数为CPU执行单元的掩码。掩码以位来表示,32系统为32位,最低位表示第一个处理器单元,第二位表示第二个处理器单元,依次类推,所以第一个处理器掩码为0x00000001,第二个为0x00000002,第三个为0x00000004,第四个为0x00000008.换算成二进制,也就是从最低位依次将标志向高位移了一位,来切换指定的CPU。
    所以,指定第三个处理器的代码为:

SetThreadAffinityMask(hT,0x00000004);//hT为线程句柄

    到这里,线程创建和设置的就完成了,剩下的就是写线程函数了。这部分的代码如下:

#include <Windows.h> 
void main() 

    SetPriorityClass(GetCurrentProcess(), 31); 
    HANDLE hT; 
    hT= CreateThread(NULL,0,ThreadProc,0,0,0); 
    SetThreadPriority(hT,31); 
    SetThreadAffinityMask(hT,0x00000003); 
    while(1)Sleep(10000);//主线程不停的循环睡眠,让线程们有时间执行 

    如果是控制一个稳定的直线走势,前面已经讲出来了。所以线程函数就是这样的: 
DWORD WINAPI ThreadProc1(LPVOID lpParam) 

    while(1) 
    { 
        for (int i=0;i<sum;i++);//控制总体的高度 
        Sleep(2);//控制局部振幅 
    } 
}

    如果要制造效果图中的几个波形,则需要产生交替执行的代码。也就是控制两个利用率水平的周期性切换。其实很简单,那就是对2取模运算,然后每一个情况执行一段时间,然后就出现了交替的效果。因为你在线程中执行,没有办法使用第三方的计时器,所以我们可以利用维持利用率的暂停时间来计数实现间隔时间的统计,使用一个变量即可,根据累计的值就知道每一种情况执行的总时间,来做切换了。这一招还是比较巧妙的,在增加额外开销的情况下,充分利用了维持利用率的暂停时间。如果没有经验,可能一下子想不到这里来。
    需要说明的一点,利用率水平高的,执行的循环次数要多很多,基本上是一个数量级的样子。这样也就使得要保持波形高低的两个宽度一致,不能简单的取相等时间。在取模的两种情况中,每一种的执行时间长度也就决定波形宽度。而循环次数少的,也就要多执行一些时间,才能将宽度不足,保持高低波形的宽度一致。不过我们这里都是一个大致的值,看上去差不多就行了。我们对于高度上的CPU利用率和水平上的波形宽度都不好精确控制的。
    为了方便产生不同的波形,我已经将代码的影响波形的关键值提取成参数,这样只要修改参数就可以改变波形了。产生曲线的代码也封装为了一个函数,代码如下:

void DrawLine(int height,bool alter,int span,int rate) 

    int f=0; 
    int count=0,count2=0; 
    int value = 1000000*height; 
    int sum=value*(alter?10:1); 
    while(1) 
    { 
        if (f%2!=0) 
        { 
             
            for (int i=0;i<sum;i++);//控制总体的高度 
            Sleep(2);//控制局部振幅 
            count++; 
            if (count>span)//控制一个波持续的长度 
            { 
                count=0; 
                f++; 
            } 
        } 
        else 
        { 
            for (int i=0;i<value;i++);//控制总体的高度 
            Sleep(2);//控制局部振幅 
            count2++; 
            if (count2>span*6*rate)//控制一个波持续的长度 
            { 
                count2=0; 
                f++; 
            } 
        } 
    } 
}


    效果图中的几个波形,分别是下面四个函数调用产生的:

DrawLine(5,false,200,1);//直线走势 
DrawLine(5,true,20,1);//等距离尖波 
DrawLine(5,true,200,1);//等距离方波 
DrawLine(5,true,50,6);//非等距离尖波


    第一个参数决定走势图的高度,也就是利用率,第二个参数决定是否要交替产生两组利用率,第三个参数决定波的宽度,第四个参数决定两个波的宽度比例,1则是相等的,大于1的就是宽度的倍数。
    我的这种实现方法讲完了,非常详细,不过局限性还是有的。如果你有兴趣,可以继续改进。这其实是微软亚洲研究院的笔试题。可惜暂时我只做到了这一步。如果你想这个效果与系统无关,与CPU无关,能够同时在多个处理器单元显示不同的效果,而且还要实现正弦波这样的曲线走势,我们这个实现不能满足,你需要继续改进。如果你做出来了,请分享出来吧,一起学习。

文章来源:C++技术网 原创文章版权所有,未经授权,禁止转载。

关注微信号,回复“干货”获取干货下载地址(干货即项目源码哦!)或者登陆个人中心下载


 
CPP技术网 更多文章 宏、常量、枚举、结构体和共用体对比分析之常量 宏、常量、枚举、结构体和共用体对比分析之结构体 C/C++声明定义初始化和赋值独家剖析深刻理解 mfc的消息机制,多窗口的互动 MFC中的窗口类:C++类与窗口句柄的结合深入浅出分析
猜您喜欢 钱途广阔的互联网金融,PHPer应该来看看 今晚7点不见不散:直播,PHP5面向对象编程入门第二讲 巨龙的宝宝,居然是个……小不点!|科学人 Java应用线上问题排查的常用工具和方法 要么做、要么留,千万不要混!