2010. 7. 24. 00:29

멀티쓰래드에서의 CPU 점유율 관리를 위한 코드 구현

제목처럼, 멀티쓰레드에서의 CPU 점유율 관리를 위한 방법을 알려드립니다.
물론, 싱글쓰레드도 포함됩니다.

CPU 점유율 관리란,

  • 자신의 프로그램이 CPU를 많이 사용하여, 이를 낮추는 것
  • 자신의 프로그램은 아니지만, 다른 프로그램에서 CPU를 많이 사용하여,
    양보를 통해 자신의 프로그램이 좀더 원할하게 실행되는 것

으로 생각해 보도록 하겠습니다.

생각해 볼 건, CPU 점유율을 어떻게 가져오느냐 입니다.
간단히 pdh.dll의 함수군을 이용하면 쉽게 구할 수 있습니다.
그러면, GetCurrentCPUUsage(...) 함수를 만들 수 있습니다.
(해당 본 포스트 마지막 부분의 CCPUUsage::GetCPUUsage(...)를 참고하시기 바랍니다.)

해당 함수를 가지고,

...	
for (...)
	{
		// CPU 점유율을 높이는 코드들 실행...
		...
		if (GetCurrentCPUUsage() > 80)
		{
			::Sleep(10);	// 현재 CPU 점유율이 높다면, Sleep(...) 실행
		}
		...

와 같이 구현하면 됩니다.
물론, 위 사항은 단일쓰레드에서 적합한 경우일 것으로 확인됩니다.
왜냐하면, pdh.dll 함수군으로 CPU 사용량을 구하는데, 이 자체도 CPU 자원을 소모하게 됩니다.
멀티쓰레드로 동시에 많은 GetCurrentCPUUsage(...)가 호출이 되면, 심각하게 성능이 떨어짐이 발생합니다.
즉, 위 코드가 멀티쓰레드로 동시에 여러개가 실행이 되면,
::Sleep(...)을 주석으로 하더라도 40%~50%의 점유율이 될 수 있습니다.
물론, GetCurrentCPUUsage(...)가 없다면 100%를 가르키게 됩니다.

따라서, 멀티쓰레드 환경을 위해서는, 필요할때 GetCCurrentCPUUsage(...)를 호출하는 것이 아니라,
임시의 Thread에서 주기적으로 GetCurrentCPUUsage(...)를 한번씩 호출하고,
해당 값을 공유 메모리에 쓴뒤, 각 Thread에서는 해당 값만 참고하여 현재의 CPU 사용율을 구해오는 방식이
구현되어야 합니다.
(잘 생각해보면, 비단 멀티쓰레드인 경우 뿐만 아니라, 단일 쓰레드에서 GetCurrentCPUUsage(...)를
굉장히 자주 호출되는 경우도 포함됩니다.)

자, 이렇게 구현이 되면, 충분히 CPU 점유율을 컨트롤하면서 실행이 잘 될거 같지만,
실제로는 80%->10% -> 90% -> 5% -> ...
와 같이 매우 불안정한 CPU 점유율로 진행이 됩니다.
이걸 좀더 안정화 시키기 위해서는 순간의 CPU 점유율을 가져오는 것이 아니라,
최근 얼마 동안의 최고 CPU 점유율로 판단하는 것이 현명합니다.
(물론, 어쩔수 없이 Peak를 치는 경우도 발생하지만, 현재의 CPU 점유율로 판단하는 것보다는 훨씬 매끈합니다.)

이를 지원하는 Class인 CCPUUsage를 소개합니다.

본, Class를 사용하는 Skeleton은 다음과 같습니다.
#include "CPUUsage.h"
...
INT g_nMaxCPUUSage = 0;	// 되도록 전역 보다는 main(...)안이든, MFC의 theApp 안이든,...에 둔다.
INT g_nCurCPUUSage = 0;
...
int main(...)
{
	...
	CCPUUSage cCPUUsage;	// 객체 생성 (주로 Primary Thread에서...)
	...

	if (TRUE == cCPUUSage.Init())
	{
		// CCPUUSage 초기화 성공
		// CPU Usage를 주기적으로 쓸 메모리 번지를 전달한다.
		// 0.5초(500ms)마다 한번씩 그때의 CPU 사용율을 g_nCurCPUUSage에 쓰고,
		// 5초(0.5초x10회)초 동안 최고의 CPU 사용율을 g_nMaxCPUUSage에 쓴다.
		cCPUUSage.StartWriteCPUUsage(&g_nCurCPUUSage, &g_nMaxCPUUSage, 500, 10);
	}
	...
	{
		// 만일 Thread를 여러개 생성한다면,..
		// Thread 함수명은 ThreadFunc로...

	INT ThreadFunc(IN LPVOID pstThreadFunc)
	{
		for (;;)
		{
			...
			MainWorkingFunc();

			if (g_nCurCPUUSage > 80)
			{
				::Sleep(10);
			}
		}
	}
...
}

위와 같이 쉽게 사용가능한 class(CPUUsage.h/CPUUsage.cpp)를 공유합니다.