问题:
写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率(单核)。有以下几种情况:
1.CPU占用率固定在50%,为一条直线
2.CPU的占用率为一条直线,具体占用率由命令行参数决定(范围1~100)
3.CPU的占用率状态为一条正弦曲线
4.多核处理器情况下上述问题怎么解决
分析与解答
首先确定CPU占用率的定义,即在任务管理器的一个刷新周期内,CPU忙(执行应用程序)的时间和刷新周期总时间的比率,就是CPU的占用率,也可以说成,任务管理器中显示的是每个刷新周期内CPU占用率的统计平均值。
所以可以写个程序,在一个刷新周期中,一会儿忙,一会儿闲,调节忙/闲比例,就可以控制CPU占有率了。
一个刷新时间是多久,书上说,通过对任务管理器观测,大约是1秒。鼠标移动、后台程序等都会对曲线造成影响!
单核环境下,空死循环会导致100%的CPU占用率。双核环境下,CPU总占用率大约为50%,四核是25%左右。
解法一:简单解法
Busy用可循环来实现,for(i=0;i<n;i++) ;
对应的汇编语言为
loop; mov dx i ;将i置入dx寄存器 inc dx ;将dx寄存器加1 mov dx i ;将dx中的值赋回i cmp i n ;比较i和n j1 loop ;i小于n时则重复循环
我的cpu是 I5 2410M 2.30GHZ(双核四线程,如图) 因为目前的cpu每个时钟周期可执行两条以上的代码,取平均值2,于是(2300000000*2)/5=920000000(循环/秒) 每秒可以执行循环920000000次。
不能简单的取n=920000000然后sleep(1000),如果让cpu工作1s,休息1s很可能是锯齿,先达到一个峰值然后跌入一个很低的占有率;所以我们睡眠时间改为100ms,100ms比较接近windows的调度时间,n=92000000。如果sleep时间选的太小,会造成线程频繁的唤醒和挂起,无形中增加了内核时间的不确定性因此代码如下:
#include <windows.h> int main(void) { <span style="white-space:pre"> </span>//Run on CPU 0(0x00000001)(00000001) <span style="white-space:pre"> </span>//Run on CPU 1(0x00000002)(00000010) <span style="white-space:pre"> </span>//Run on CPU 0 AND CPU 1(0x00000003)(00000101) <span style="white-space:pre"> </span>//Run on CPU 2(0x00000004)(00000100) <span style="white-space:pre"> </span>//...... <span style="white-space:pre"> </span>//SetProcessAffinityMask(GetCurrentProcess(),0x1);//进程与指定cpu绑定 <span style="white-space:pre"> </span>SetThreadAffinityMask(GetCurrentThread(), 0x1);//线程与指定cpu绑定 <span style="white-space:pre"> </span>while(true) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>for(int i=0;i<92000000;i++) <span style="white-space:pre"> </span>; <span style="white-space:pre"> </span>Sleep(100); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>return 0; }
使用SetProcessAffinityMask函数,进程与CPU绑定,得到如下图。
使用SetThreadAffinityMask函数,进程与CPU绑定,得到如下图。
解法二:使用GetTickCount()和Sleep()
GetTickCount()可以得到“系统启动到现在”所经历的时间的毫秒值,最多可以统计49.7天,可以利用GetTickCount()判断循环的时间,代码如下:
#include <windows.h> const int busyTime=100; const int idleTime=busyTime; int main(void) { double startTime; SetProcessAffinityMask(GetCurrentProcess(), 0x1); while(true) { startTime=GetTickCount(); while((GetTickCount() - startTime) <= busyTime) { ; } Sleep(idleTime); } return 0; }
效果如下图所示,与第一种解法效果差不多,因为都假设当前系统只有当前程序在运行,但实际上,操作系统有很多程序会同时调试执行各种任务,如果此刻进程使用20%的cpu,那我们的程序只有使用30%的cpu才能达到50%的效果。
解法三:能动态适应的解法
使用.Net Framework提供的PerformanceCounter进行查询。具体代码是用C#写的
using System; using System.Diagnostics; namespace CPU { class Program { static void Main(string[] args) { cpu(1000); } static void cpu(double level) { PerformanceCounter p = new PerformanceCounter("Processor", "% Processor Time", "_Total"); if (p == null) { return; } while (true) { if (p.NextValue() > level) System.Threading.Thread.Sleep(1000); } } } }
解法四:正弦曲线
#include <windows.h> #include <math.h> int main(void) { SetProcessAffinityMask(GetCurrentProcess(), 0x1); const double SPLIT=0.01; const int COUNT=200; const double PI=3.14159265; const int INTERVAL=300; DWORD busySpan[COUNT]; //array of busy time DWORD idleSpan[COUNT]; //array of idle time int half=INTERVAL/2; double radian=0.0; for(int i=0;i<COUNT;i++) { busySpan[i]=(DWORD)(half+(sin(PI*radian)*half)); idleSpan[i]=INTERVAL-busySpan[i]; radian+=SPLIT; } DWORD startTime=0; int j=0; while(true) { j=j%COUNT; startTime=GetTickCount(); while((GetTickCount()-startTime)<=busySpan[j]) ; Sleep(idleSpan[j]); j++; } return 0; }
进一步讨论:
控制CPU占用率,因为要调用Windows的API,要考虑到多核、超线程的情况,要考虑到不同版本的Windows的计时相关的API的精度不同,使问题变得相当复杂,若再考虑其它程序的CPU占用率,则该问题则变得很烦人。(taskmgr调用了一个未公开的API)。对CPU核数的判断,书上是调用GetProcessorInfo,其实可以直接调用GetSystemInfo,SYSTEM_INFO结构的dwNumberOfProcessors成员就是核数。
如果不考虑其它程序的CPU占用情况,可以在每个核上开一个线程,运行指定的函数,实现每个核的CPU占用率相同。如果CPU占用率曲线不是周期性变化,就要对每个t值都要计算一次,否则,可以只计算第一个周期内的各个t值,其它周期的直接取缓存计算结果。
#include<iostream> #include<cmath> #include<windows.h> static int PERIOD = 60 * 1000; //周期ms const int COUNT = 300; //一个周期计算次数 const double GAP_LINEAR = 100; //线性函数时间间隔100ms const double PI = 3.1415926535898; //PI const double GAP = (double)PERIOD / COUNT; //周期函数时间间隔 const double FACTOR = 2 * PI / PERIOD; //周期函数的系数 static double Ratio = 0.5; //线性函数的值 0.5即50% static double Max=0.9; //方波函数的最大值 static double Min=0.1; //方波函数的最小值 typedef double Func(double); //定义一个函数类型 Func*为函数指针 typedef void Solve(Func *calc);//定义函数类型,参数为函数指针Func* inline DWORD get_time() { return GetTickCount(); //操作系统启动到现在所经过的时间ms } double calc_sin(double x) //调用周期函数solve_period的参数 { return (1 + sin(FACTOR * x)) / 2; //y=1/2(1+sin(a*x)) } double calc_fangbo(double x) //调用周期函数solve_period的参数 { //方波函数 if(x<=PERIOD/2) return Max; else return Min; } void solve_period(Func *calc) //线程函数为周期函数 { double x = 0.0; double cache[COUNT]; for (int i = 0; i < COUNT; ++i, x += GAP) cache[i] = calc(x); int count = 0; while(1) { unsigned ta = get_time(); if (count >= COUNT) count = 0; double r = cache[count++]; DWORD busy = r * GAP; while(get_time() - ta < busy) {} Sleep(GAP - busy); } } void solve_linear(Func*) //线程函数为线性函数,参数为空 NULL { const unsigned BUSY = Ratio * GAP_LINEAR; const unsigned IDLE = (1 - Ratio) * GAP_LINEAR; while(1) { unsigned ta = get_time(); while(get_time() - ta < BUSY) {} Sleep(IDLE); } } void run(int i=1,double R=0.5,double T=60000,double max=0.9,double min=0.1) //i为输出状态,R为直线函数的值,T为周期函数的周期,max方波最大值,min方波最小值 { Ratio=R; PERIOD=T; Max=max; Min=min; Func *func[] = {NULL ,calc_sin,calc_fangbo}; //传给Solve的参数,函数指针数组 Solve *solve_func[] = { solve_linear, solve_period}; //Solve函数指针数组 SYSTEM_INFO info; GetSystemInfo(&info); //得到cpu数目 int NUM_CPUS = info.dwNumberOfProcessors; HANDLE *handle = new HANDLE[NUM_CPUS]; DWORD *thread_id = new DWORD[NUM_CPUS]; //线程id switch(i) { case 1: //cpu0 ,cpu1都输出直线 { for (int i = 0; i < NUM_CPUS; ++i) { Func *calc = func[0]; Solve *solve = solve_func[0]; if ((handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, (VOID*)calc, 0, &thread_id[i])) != NULL) //创建新线程 SetThreadAffinityMask(handle[i], i); //限定线程运行在哪个cpu上 } WaitForSingleObject(handle[0],INFINITE); //等待线程结束 break; } case 2: //cpu0直线,cpu1正弦 { if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[0], (VOID*)func[0], 0, &thread_id[1])) != NULL) //创建新线程 SetThreadAffinityMask(handle[1], 1); //限定线程运行在哪个cpu上 Func *calc = func[1]; Solve *solve = solve_func[1]; if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, (VOID*)calc, 0, &thread_id[0])) != NULL) //创建新线程 SetThreadAffinityMask(handle[0], 2); //限定线程运行在哪个cpu上 WaitForSingleObject(handle[0],INFINITE); //等待线程结束 break; } case 3: //cpu0直线,cpu1方波 { if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[0], (VOID*)func[0], 0, &thread_id[0])) != NULL) //创建新线程 SetThreadAffinityMask(handle[0], 1); //限定线程运行在哪个cpu上 Func *calc = func[2]; Solve *solve = solve_func[1]; if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, (VOID*)calc, 0, &thread_id[1])) != NULL) //创建新线程 SetThreadAffinityMask(handle[1], 2); //限定线程运行在哪个cpu上 WaitForSingleObject(handle[0],INFINITE); //等待线程结束 break; } case 4: //cpu0正弦,cpu1方波 { if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[1], (VOID*)func[1], 0, &thread_id[0])) != NULL) //创建新线程 SetThreadAffinityMask(handle[0], 1); //限定线程运行在哪个cpu上 Func *calc = func[2]; Solve *solve = solve_func[1]; if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, (VOID*)calc, 0, &thread_id[1])) != NULL) //创建新线程 SetThreadAffinityMask(handle[1], 2); //限定线程运行在哪个cpu上 WaitForSingleObject(handle[0],INFINITE); //等待线程结束 break; } default: break; } } void main() { //run(1,0.5); //cpu1 ,cpu2都输出50%的直线 run(2,0.5,30000); //cpu1 0.5直线,cpu2正弦周期30000 //run(3); //cpu1直线,cpu2方波 //run(4,0.8,30000,0.95,0.5); //cpu1正弦,cpu2 0.95-0.5的方波 }
这是是第一次CPU跑直线,第二个CPU跑正弦函数~~~~~~~~·