找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3112

积分

0

好友

426

主题
发表于 21 小时前 | 查看: 2| 回复: 0

前面介绍过将进程或线程绑定到特定CPU核心的方法,但单纯的绑定有时还不够。因为虽然调度器不会再把你的线程调度到其他核心,但其他线程仍然可以被调度到你绑定的核心上,与你争夺计算资源。

那么,有没有办法能让某个核心“专属于”你的线程,彻底杜绝干扰呢?

在Linux上,这相对简单,可以通过内核启动参数 isolcpus 来隔离指定CPU。被隔离的核心默认不会用于调度普通进程,你可以通过 taskset 或程序中的 sched_setaffinity 等函数,手动将你的进程/线程绑定上去,从而实现近乎独占的效果(内核自身的少量后台任务仍会运行)。

实际上,Windows系统也提供了类似的功能,只不过其配置方式是通过修改注册表来实现的。具体操作步骤如下:

  1. 打开注册表编辑器 (regedit)
  2. 导航到以下路径:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\kernel
  3. 在该路径下,检查是否存在名为 ReservedCpuSets 的注册表项。如果不存在,则需要新建一个,类型为 二进制值 (REG_BINARY)
  4. 设置 ReservedCpuSets 的值。这个二进制值的每一位对应一个逻辑处理器(CPU核心),从低位(第0位)开始。例如,在一台8核电脑上,如果想隔离最后两个核心(CPU6和CPU7),需要设置的二进制数值为 0b11000000,对应的十六进制数为 0xC0。在编辑时,通常输入十六进制值即可。

Windows注册表编辑器设置ReservedCpuSets值为0xC0

  1. 重启计算机使配置生效。

重启后,打开任务管理器查看CPU利用率,你会发现被隔离的核心(本例中的CPU6和CPU7)利用率几乎为0,与其他核心形成鲜明对比。这证明调度器已不再主动将线程调度到这两个核心上。

任务管理器显示CPU6与CPU7利用率近乎为0

为了进一步验证,可以运行10个死循环程序。观察任务管理器,即使其他6个核心的利用率已被打满,被隔离的两个核心依然“稳如泰山”,不受影响。

运行死循环后,被隔离的CPU6与CPU7利用率仍为0

那么,在程序中如何识别哪些CPU已被隔离呢?这就要用到Windows的CPU Sets相关API,特别是 GetSystemCpuSetInformation 函数。它能获取系统中所有CPU Set的信息,其结构体 SYSTEM_CPU_SET_INFORMATION 中的 Allocated 成员就表明了该CPU是否已被分配(隔离)。运行以下程序即可查看:

#include<windows.h>
#include<stdio.h>
#include<stdlib.h>

int main(int argc, char *argv[])
{
  DWORD size = 0;
  DWORD count = 0;
  PSYSTEM_CPU_SET_INFORMATION info = NULL;

  // 第一次调用,获取所需缓冲区大小
  BOOL success = GetSystemCpuSetInformation(NULL, 0, &size, GetCurrentProcess(), 0);
  if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
    printf("Failed to get buffer size, error: %lu\n", GetLastError());
    return 1;
  }

  // 分配缓冲区
  info = (PSYSTEM_CPU_SET_INFORMATION)malloc(size);
  if (info == NULL) {
    printf("Memory allocation failed\n");
    return 1;
  }

  // 第二次调用,获取实际信息
  success = GetSystemCpuSetInformation(info, size, &size, GetCurrentProcess(), 0);
  if (!success) {
    printf("Failed to get CPU set information, error: %lu\n", GetLastError());
    free(info);
    return 1;
  }

  // 计算 CPU 集数量
  DWORD cpuSetCount = size / sizeof(SYSTEM_CPU_SET_INFORMATION);

  printf("%-10s %-12s %-10s %-15s %-15s %-15s\n",
    "CPU Index", "CpuSet ID", "Group", "Core Index", "Logical Index", "Allocated");
  printf("============================================================================\n");

  // 遍历并打印信息(每个 CPU 一行)
  DWORD validCpuCount = 0;
  for (DWORD i = 0; i < cpuSetCount; ++i) {
    if (info[i].Type == CpuSetInformation) {
      validCpuCount++;

      printf("%-10lu %-12lu %-10hu %-15hu %-15hu %-15hu\n",
        validCpuCount - 1,  // CPU编号(从0开始)
        info[i].CpuSet.Id,
        info[i].CpuSet.Group,
        info[i].CpuSet.CoreIndex,
        info[i].CpuSet.LogicalProcessorIndex,
        info[i].CpuSet.Allocated
      );
    }
  }

  // 计算并显示 CPU 总数
  printf("============================================================================\n");
  printf("Total logical processors: %lu\n", validCpuCount);

  free(info);
  return 0;
}

程序运行结果如下,可以看到只有CPU6和CPU7对应的 Allocated 值为1,证实它们已被隔离。

GetSystemCpuSetInfo.exe运行结果,显示CPU6与CPU7的Allocated为1

核心已被隔离,接下来就是如何将程序运行在上面。实际上,之前介绍的线程绑定方法依然有效。例如,可以通过任务管理器,右键点击目标进程,选择“设置相关性”,然后勾选已被隔离的核心(如CPU7)。

在任务管理器中右键进程选择“设置相关性”

在“处理器相关性”对话框中勾选已被隔离的CPU7

设置后再次观察,你会发现CPU7的利用率立刻飙升到100%,而同样被隔离但未绑定任何进程的CPU6,其利用率依然为0。

绑定死循环进程到CPU7后,其利用率达到100%

除了使用任务管理器,更常用的方式是在程序中直接调用Windows API,例如 SetThreadAffinityMask。下面的代码演示了如何将当前线程绑定到已被隔离的CPU6上:

#include<windows.h>
#include<stdio.h>

int main(int argc, char* argv[])
{
  HANDLE thread = GetCurrentThread();

  // 设置线程亲和性掩码,将线程绑定到第6个CPU核心(CPU6,从0开始计数)
  DWORD_PTR affinityMask = 1 << 6;
  DWORD_PTR prevAffinityMask = SetThreadAffinityMask(thread, affinityMask);
  if (prevAffinityMask == 0)
  {
    DWORD dwError = GetLastError();
    printf("SetThreadAffinityMask failed! Error code: %lu\n", dwError);
    return 1;
  }

  printf("Thread affinity set successfully!\n");
  printf("Previous affinity mask: 0x%I64X\n", prevAffinityMask);
  printf("Current affinity mask: 0x%I64X (CPU 6)\n", affinityMask);

  while (1) {}

  return 0;
}

该程序将自己绑定到CPU6后执行死循环,通过任务管理器可以看到CPU6的利用率随即变为100%。

通过SetThreadAffinityMask绑定线程到CPU6后,CPU6利用率达100%

需要注意:之前介绍的Windows CPU Sets相关API(如 SetProcessDefaultCpuSets)并不能将线程绑定到已被 ReservedCpuSets 隔离的核心上。即使调用成功,也不会生效,线程仍然可能被调度到其他非隔离核心。

总结

要实现线程对CPU核心的独占,需要两步:

  1. 隔离目标CPU核心:阻止系统调度器将普通线程分配上去。
    • Linux: 通过内核启动参数 isolcpus 实现。
    • Windows: 通过修改注册表 HKEY_LOCAL_MACHINE\SYSTEM...\kernel\ReservedCpuSets 实现。
  2. 将目标线程绑定到已隔离的核心
    • Linux: 使用 taskset 命令或 sched_setaffinity 等系统调用。
    • Windows: 使用任务管理器或 SetThreadAffinityMask 等API。

这种方法适用于对性能抖动极其敏感、需要实现软实时效果,或希望特定关键任务完全不受其他进程干扰的场景。如果你正在从事高性能计算、低延迟交易或实时音视频处理等领域的多线程开发,不妨尝试一下。

本文介绍的通过 ReservedCpuSets 注册表隔离CPU的方法,其思路来源于 GitHub 项目:https://github.com/valleyofdoom/ReservedCpuSets,在此表示感谢。




上一篇:干货分享:详解以太网100米传输限制与10种超距组网方案
下一篇:Docker容器中搭建CUDA与CUDNN开发环境:Ubuntu 20.04完整步骤
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 22:12 , Processed in 0.415715 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表