更好的睡眠方式:控制线程的执行并限制 CPU 使用率
这是一个类,它将帮助您控制涉及循环/轮询的线程的执行,并且还将其 CPU 使用率限制为指定的限制。
引言
我有一个由 CPU 密集型线程组成的代码。 我需要限制它的执行,以便其 CPU 使用率不会超过特定限制,而不管程序运行的硬件/处理器是什么。
此外,我们经常会遇到一个问题,我们需要Sleep()
一个线程特定持续时间。 这种代码通常由一个循环组成,进行某种处理/重复轮询,并在循环结束时休眠一段时间以节省 CPU 周期。 这种情况一直持续到循环终止。 例如,考虑一个屏幕捕获程序,该程序使用BitBlt
不断抓取桌面图像,并将屏幕截图发送到某个网络套接字或队列。 在这样的程序中,一个线程专门用于抓取屏幕并在循环中发送它。 现在BitBlt
是一个非常 CPU 密集型的操作,而将数据放入队列是一个轻量级的操作。 因此,此线程将大部分时间花费在Bitblt
中,这将持续占用 CPU,从而使其他进程无法使用 CPU。
Sleep()
解决方案可能还可以,但不是正确的解决方案,因为使用Sleep()
我们无法控制分配给该线程的处理能力的带宽。 因此,在高配置 CPU 上,这些线程将获得非常少的处理周期,因为大部分有用的 CPU 周期将在休眠期间被浪费掉,而在较慢的处理器上,这些 CPU 密集型线程将无法为其他进程提供足够的 CPU 资源。
这是一个类,它将限制线程的 CPU 使用率到指定的限制,并帮助控制其执行。 可以说,它是Sleep()
更好的替代方案。
Using the Code
此代码的核心逻辑如下。
我们代码的主要目的是将线程的平均 CPU 使用率保持在指定的限制。 此限制是线程所花费的用户时间和内核时间之和与系统所花费的空闲、用户和内核时间之和的百分比。
因此,假设在时间t1
时,线程的 CPU 使用率为To
,系统的 CPU 使用率为So
并且假设在时间t2
时,线程的 CPU 使用率为Tn
,系统的 CPU 使用率为Sn
并且假设指定的比率/百分比为R
。
So (Tn-To)/(Sn-So) <= R/100
=> (Tn-To) X 100<=(Sn-So) X R
=> (Tn-To) X 100 - (Sn-So) X R <= 0
因此,如果该等式大于零,那么我们可以说该限制已超过。
此外,假设我们需要休眠Z
时间段,以便平均 CPU 使用率百分比低于指定限制。 所以
(Tn-To)/((Sn-So) + Z) = R/100
=> (Tn-To) X 100 = ((Sn-So) + Z) X R
=> (Tn-To) X 100 = (Sn-So) X R + Z X R
=> (Tn-To) X 100 - (Sn-So) X R = Z X R
=> (Tn-To) X (100/R) - (Sn-So) = Z
这显示了我们如何计算我们需要休眠的时间段,以将处理器使用率保持在限制范围内。
这里核心类是CPULimiter
。 大部分代码都是不言自明的。 这个类主要的功能是进行上述所有计算并让线程休眠是CalculateAndSleep()
。
为了获取线程和系统 CPU 使用率计时,分别使用了GetThreadTimes
和GetSystemTimes
Win32 API。
这是CPULimiter
类的结构
const int DEFAULT_MAX_PERCENTAGE = 20;
/*
CPULimiter:
This class helps to limit the CPU usage/consumption by a thread involving
some kind of repetitive/polling kind of operation in a loop.
The limit can be set by a user through a function of this class.
*/
class CPULimiter
{
//This integer stores last total system time.
//total system time is sum of time spent by system
//in kernel, user and idle mode
LARGE_INTEGER m_lastTotalSystemTime;
//This integer stores last total time spent by this
//thread in kernel space and user space
LARGE_INTEGER m_lastThreadUsageTime;
//Variable used to store maximum thread CPU percentage
//relative to system total time.
int m_ratio;
public:
//Constructors
CPULimiter();
//Constructor with maximum thread cpu usage percentage
CPULimiter(int p_ratio);
//****************Main function.******************
//It evaluates CPU consumption by this thread since
//last call to this function, until now.
//Internally, it calculates if the thread has exceeded
//the maximum CPU usage percentage specified.
//if yes, it makes the thread sleep for a calculated
//time period, to average the total usage to the limit specified.
//Returns TRUE Successful, else FALSE
BOOL CalculateAndSleep();
//Inline setter function
inline void SetRatio(int p_ratio){m_ratio = p_ratio;}
};
这是CalculateAndSleep
函数
BOOL CPULimiter::CalculateAndSleep()
{
//Declare variables;
FILETIME sysidle, kerusage, userusage, threadkern
, threaduser, threadcreat, threadexit;
LARGE_INTEGER tmpvar, thissystime, thisthreadtime;
//Get system kernel, user and idle times
if(!::GetSystemTimes(&sysidle, &kerusage, &userusage))
return FALSE;
//Get Thread user and kernel times
if(!::GetThreadTimes(GetCurrentThread(), &threadcreat, &threadexit
, &threadkern, &threaduser))
return FALSE;
//Calculates total system times
//This is sum of time used by system in kernel, user and idle mode.
tmpvar.LowPart = sysidle.dwLowDateTime;
tmpvar.HighPart = sysidle.dwHighDateTime;
thissystime.QuadPart = tmpvar.QuadPart;
tmpvar.LowPart = kerusage.dwLowDateTime;
tmpvar.HighPart = kerusage.dwHighDateTime;
thissystime.QuadPart = thissystime.QuadPart + tmpvar.QuadPart;
tmpvar.LowPart = userusage.dwLowDateTime;
tmpvar.HighPart = userusage.dwHighDateTime;
thissystime.QuadPart = thissystime.QuadPart + tmpvar.QuadPart;
//Calculates time spent by this thread in user and kernel mode.
tmpvar.LowPart = threadkern.dwLowDateTime;
tmpvar.HighPart = threadkern.dwHighDateTime;
thisthreadtime.QuadPart = tmpvar.QuadPart;
tmpvar.LowPart = threaduser.dwLowDateTime;
tmpvar.HighPart = threaduser.dwHighDateTime;
thisthreadtime.QuadPart = thisthreadtime.QuadPart + tmpvar.QuadPart;
//Check if this is first time this function is called
//if yes, escape rest after copying current system and thread time
//for further use
//Also check if the ratio of differences between current and previous times
//exceeds the specified ratio.
if( thisthreadtime.QuadPart != 0
&& (((thisthreadtime.QuadPart - m_lastThreadUsageTime.QuadPart)*100)
- ((thissystime.QuadPart - m_lastTotalSystemTime.QuadPart)*m_ratio)) > 0)
{
//Calculate the time interval to sleep for averaging the extra CPU usage
//by this thread.
LARGE_INTEGER timetosleepin100ns;
timetosleepin100ns.QuadPart = (((thisthreadtime.QuadPart
- m_lastThreadUsageTime.QuadPart)*100)/m_ratio)
- (thissystime.QuadPart
- m_lastTotalSystemTime.QuadPart);
//check if time is less than a millisecond, if yes, keep it for next time.
if((timetosleepin100ns.QuadPart/10000) <= 0)
return FALSE;
//Time to Sleep :)
Sleep(timetosleepin100ns.QuadPart/10000);
}
//Copy usage time values for next time calculations.
m_lastTotalSystemTime.QuadPart = thissystime.QuadPart;
m_lastThreadUsageTime.QuadPart = thisthreadtime.QuadPart;
return TRUE;
}
这是一个使用此类的 CPU 密集型代码示例
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<"This is a cpu intensive program";
//define CPULimiter variable
//and limit cpu usage percentage to 5% here.
CPULimiter limiter = 5;
int a,b;
a = 78;
//Some CPU intensive code here....
while(1)
{
b = a;
//limit cpu usage here.
limiter.CalculateAndSleep();
}
return 0;
}
注意:在这里,我们限制使用率为 5%。 如果我们没有这样做,它将会占用所有 CPU。
CPULimiter limiter = 5;
历史
- 2008 年 8 月 27 日:最初的帖子