Windows上无法直接获取CPU核心毫秒级使用率,因系统未提供开箱即用API;最稳定方案是用PDH API(PdhAddCounter/PdhCollectQueryData)按逻辑核心采样,精度10–100ms,无需管理员权限,并需通过GetLogicalProcessorInformation映射物理拓扑。

为什么直接读取CPU核心使用率在Windows上不简单
Windows没有提供开箱即用的API直接返回“每个核心当前毫秒级使用率”,GetSystemTimes 和 QueryPerformanceCounter 只给全局统计;WMI(如 Win32_PerfFormattedData_PerfOS_Processor)延迟高、开销大,且默认只暴露 _Total 和单个逻辑处理器索引,不保证与物理核心一一对应。真实场景中,你要的是:每100ms能拿到每个逻辑核心(含超线程)的最近周期内占用百分比,且不能依赖管理员权限或额外服务。
用 PdhAddCounter + PdhCollectQueryData 获取每个逻辑核心的实时采样
这是最稳定、低开销、无需管理员权限的方案。PDH(Performance Data Helper)底层调用内核性能计数器,精度可达10–100ms,且支持按 "\Processor(0)\% Processor Time" 这类路径精确指定逻辑核心。
- 初始化时用
PdhOpenQuery创建查询句柄 - 对每个核心 i,调用
PdhAddCounter(query, L"\\ComputerName\Processor(0)\% Processor Time", 0, &counter)—— 注意Processor(0)中的0是逻辑核心索引,不是物理核心编号;可用GetSystemInfo().dwNumberOfProcessors获取总数 - 首次调用
PdhCollectQueryData后需等待至少10ms再调用第二次,否则返回PDH_INVALID_DATA - 用
PdhGetFormattedCounterValue提取double类型的百分比值(范围 0.0–100.0),不是整数
示例关键片段:
PDH_HQUERY query; PdhOpenQuery(NULL, 0, &query); PDHCOUNTER counter; PdhAddCounter(query, L"\Processor(0)\% Processor Time", 0, &counter); PdhCollectQueryData(query); Sleep(10); // 必须等 PdhCollectQueryData(query); PDH_FMT_COUNTERVALUE value; PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, NULL, &value); double usage = value.doubleValue; // 如 12.3456789
如何把逻辑核心索引映射到物理核心和缓存拓扑
Windows的 Processor(N) 是逻辑处理器序号(从0开始),但用户常误以为它等于物理核心编号。实际物理布局需靠 GetLogicalProcessorInformation 解析 SYSTEM_LOGICAL_PROCESSOR_INFORMATION 结构体:
立即学习“C++免费学习笔记(深入)”;
- 遍历所有条目,筛选
RelationProcessorCore类型,每个结构体的ProcessorMask表示该物理核心上启用的逻辑线程位图 - 例如
ProcessorMask = 3(二进制11)表示物理核心0绑定了逻辑核心0和1(超线程) - 若只要“每个物理核心平均使用率”,就对属于同一
ProcessorMask的所有逻辑核心值取平均;若要区分L1/L2缓存争用,则必须保留逻辑核心粒度
注意:GetLogicalProcessorInformation 返回顺序不保证与PDH中 Processor(N) 的N一致,必须用 Group 字段和掩码做匹配,不能硬编码索引对应。
常见坑:采样频率不准、数值跳变、多线程访问冲突
PDH本身不是线程安全的——同一个 query 句柄不能被多个线程并发调用 PdhCollectQueryData,否则可能返回 PDH_INVALID_HANDLE 或乱值。
- 避免在UI线程里每10ms强制刷新:高频调用
PdhCollectQueryData会增加内核态开销,实测超过50Hz后部分系统出现采样值卡在0或100不动 - 第一次采集后立即读值是无效的(
PDH_INVALID_DATA),必须至少两次采集间隔 ≥10ms;建议固定用SetTimer或WaitForSingleObject控制节拍 - 如果程序运行在虚拟机中,
\Processor(*)\% Processor Time可能反映的是vCPU调度时间,而非宿主机物理核心真实负载,此时数值仍有效,但含义已不同
真正难的不是获取数字,而是理解那个数字到底代表什么:是过去100ms内该逻辑核心执行非空闲指令的时间占比?还是包括中断、DPC、hypervisor steal time?Windows文档没明说,但实测值与任务管理器“性能”页完全一致 —— 这就够了。


















