在Windows系统下,通过性能计数器可以精准获取进程的CPU占用情况,核心逻辑是通过两次采样进程的内核时间和用户时间,结合两次采样的时间间隔,计算出该时间段内进程占用的CPU时间比例,最终得到CPU百分比。

核心原理说明
CPU百分比的计算公式为:进程CPU占用率 = (进程本次CPU时间 - 进程上次CPU时间) / (系统本次时间 - 系统上次时间) * 100%。其中进程CPU时间包含内核态运行时间和用户态运行时间,系统时间则是系统自启动以来的累计运行时间。
性能计数器是Windows系统提供的用于监控系统性能数据的接口,我们可以通过它获取指定进程的CPU时间以及系统运行时间,避免了直接调用底层API的复杂操作。
实现步骤分解
1. 引入必要依赖
需要包含Windows系统的性能计数器相关头文件,同时链接对应的库文件,确保相关函数可以正常调用。
2. 初始化性能计数器查询
创建性能计数器查询句柄,添加需要采集的计数器项,包括目标进程的% Processor Time计数器,以及System的System Up Time计数器,用于获取系统运行时间。
3. 两次采样计算差值
间隔固定的时间(比如1秒)进行两次采样,分别获取两次采样时的进程CPU时间和系统时间,按照公式计算CPU占用百分比。
完整代码示例
以下是完整的C++实现代码,代码中包含详细注释说明每一步的作用:
#include <windows.h>
#include <pdh.h>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
// 链接性能计数器库
#pragma comment(lib, "pdh.lib")
class ProcessCpuMonitor {
private:
PDH_HQUERY query_handle; // 性能计数器查询句柄
PDH_HCOUNTER process_counter; // 进程CPU计数器句柄
PDH_HCOUNTER system_counter; // 系统运行时间计数器句柄
double last_process_time; // 上次采样的进程CPU时间
double last_system_time; // 上次采样的系统时间
bool is_first_sample; // 是否是第一次采样标记
public:
ProcessCpuMonitor(const std::wstring& process_name)
: query_handle(NULL), process_counter(NULL), system_counter(NULL),
last_process_time(0), last_system_time(0), is_first_sample(true) {
// 初始化性能计数器查询
PDH_STATUS status = PdhOpenQuery(NULL, 0, &query_handle);
if (status != ERROR_SUCCESS) {
std::wcerr << L"打开性能计数器查询失败,错误码:" << status << std::endl;
return;
}
// 添加进程CPU占用率计数器,格式为 Process(进程名)% Processor Time
std::wstring process_counter_path = L"\Process(" + process_name + L")\% Processor Time";
status = PdhAddCounter(query_handle, process_counter_path.c_str(), 0, &process_counter);
if (status != ERROR_SUCCESS) {
std::wcerr << L"添加进程CPU计数器失败,错误码:" << status << std::endl;
PdhCloseQuery(query_handle);
query_handle = NULL;
return;
}
// 添加系统运行时间计数器,格式为 SystemSystem Up Time
status = PdhAddCounter(query_handle, L"\System\System Up Time", 0, &system_counter);
if (status != ERROR_SUCCESS) {
std::wcerr << L"添加系统时间计数器失败,错误码:" << status << std::endl;
PdhCloseQuery(query_handle);
query_handle = NULL;
return;
}
}
~ProcessCpuMonitor() {
if (query_handle != NULL) {
PdhCloseQuery(query_handle);
}
}
// 获取当前CPU占用百分比,返回-1表示获取失败
double getCpuPercent() {
if (query_handle == NULL) {
return -1.0;
}
PDH_STATUS status = PdhCollectQueryData(query_handle);
if (status != ERROR_SUCCESS) {
std::wcerr << L"采集性能计数器数据失败,错误码:" << status << std::endl;
return -1.0;
}
// 获取进程CPU时间
PDH_FMT_COUNTERVALUE process_value;
status = PdhGetFormattedCounterValue(process_counter, PDH_FMT_DOUBLE, NULL, &process_value);
if (status != ERROR_SUCCESS) {
std::wcerr << L"获取进程CPU值失败,错误码:" << status << std::endl;
return -1.0;
}
// 获取系统运行时间
PDH_FMT_COUNTERVALUE system_value;
status = PdhGetFormattedCounterValue(system_counter, PDH_FMT_DOUBLE, NULL, &system_value);
if (status != ERROR_SUCCESS) {
std::wcerr << L"获取系统时间值失败,错误码:" << status << std::endl;
return -1.0;
}
double current_process_time = process_value.doubleValue;
double current_system_time = system_value.doubleValue;
// 第一次采样只记录数据,不计算百分比
if (is_first_sample) {
last_process_time = current_process_time;
last_system_time = current_system_time;
is_first_sample = false;
return 0.0;
}
// 计算时间差值
double process_time_diff = current_process_time - last_process_time;
double system_time_diff = current_system_time - last_system_time;
// 更新上次采样数据
last_process_time = current_process_time;
last_system_time = current_system_time;
// 避免除零错误
if (system_time_diff <= 0) {
return 0.0;
}
// 计算CPU百分比,注意进程CPU时间是所有核心的总和,需要除以CPU核心数得到单核心占比
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
int cpu_core_count = sys_info.dwNumberOfProcessors;
double cpu_percent = (process_time_diff / system_time_diff) * 100.0 / cpu_core_count;
return cpu_percent;
}
};
int main() {
// 这里替换为你要监控的进程名,比如notepad表示记事本进程
ProcessCpuMonitor monitor(L"notepad");
// 循环采样,每隔1秒输出一次CPU占用率
for (int i = 0; i < 10; ++i) {
double cpu_percent = monitor.getCpuPercent();
if (cpu_percent >= 0) {
std::wcout << L"当前进程CPU占用率:" << cpu_percent << L"%" << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
注意事项
- 进程名需要和任务管理器中显示的进程名一致,不包含.exe后缀,如果进程名包含特殊字符需要正确转义。
- 第一次调用
getCpuPercent函数会返回0,因为需要两次采样才能计算差值,后续调用会返回真实的CPU占用率。 - 计算时除以CPU核心数是因为性能计数器返回的进程CPU时间是所有核心的累计时间,除以核心数后得到单核心的占用比例,更符合用户认知。
- 如果目标进程不存在,添加计数器时会失败,需要提前处理进程不存在的异常情况。
常见问题解答
为什么获取的CPU百分比可能超过100%?
如果没有除以CPU核心数,在多核系统下进程如果占满多个核心,累计的CPU时间会超过系统时间间隔,导致计算结果超过100%,按照示例代码除以核心数即可避免该问题。
可以监控其他进程的CPU占用吗?
只要知道目标进程的进程名,就可以修改ProcessCpuMonitor构造函数的入参,监控任意进程的CPU占用情况,不需要管理员权限即可获取大部分进程的CPU数据。