C#中设置Processor Affinity无效的问题排查
我在亚马逊EC2云的Windows服务器上运行程序,该实例具备32个虚拟核心。为展示系统的线性可扩展性,我需要控制进程可使用的核心数量,因此尝试使用Process类的ProcessorAffinity属性实现。编写的设置代码如下:
void SetCPU(int numCPU) { var currentProcess = Process.GetCurrentProcess(); Debug.Assert(numCpu <= Environment.ProcessorCount); var str = GetProcessorAffinityString(numCPU); var serverProcessorAffinity = Convert.ToInt64(str, 2); currentProcess.ProcessorAffinity = (IntPtr)serverProcessorAffinity; } //Produces output: numCpu=4 -> 0000 0000 0000 0000 0000 0000 0000 1111 //...............: numCpu=8 -> 0000 0000 0000 0000 0000 0000 1111 1111 //...............: numCpu=16 -> 0000 0000 0000 0000 1111 1111 1111 1111 //... static string GetProcessorAffinityString(int numCPU) { var str = ""; for (int i = 0; i < Environment.ProcessorCount; i++) { if (i < numCPU) str += "1"; else str += "0"; } //Reverse char[] charArray = str.ToCharArray(); Array.Reverse(charArray); return new string(charArray); }
但运行程序时,无论设置numCpu为多少,进程始终占用全部32个核心。虽可通过资源管理器手动修改作为临时方案,但希望实现程序化设置,请问代码存在什么问题?
你的代码存在三个关键问题,导致处理器亲和性设置未生效:
1. 进程亲和性仅影响新线程,现有线程不受修改
Process.ProcessorAffinity的设置只会对调用该方法之后新创建的线程生效,程序启动时已存在的线程(比如主线程)的亲和性不会自动同步更新。如果你的程序在调用SetCPU之前已经启动了工作线程,这些线程仍会使用默认的全核心亲和性,导致进程整体看起来占用全部32个核心。
解决方法:设置进程亲和性后,遍历进程内所有现有线程,逐个设置线程级别的亲和性:
void SetCPU(int numCPU) { var currentProcess = Process.GetCurrentProcess(); Debug.Assert(numCPU <= Environment.ProcessorCount); var str = GetProcessorAffinityString(numCPU); var affinityMask = Convert.ToUInt64(str, 2); var affinityPtr = (IntPtr)(long)affinityMask; // 设置进程亲和性,影响后续新建线程 currentProcess.ProcessorAffinity = affinityPtr; // 遍历所有现有线程,同步设置线程亲和性 foreach (ProcessThread thread in currentProcess.Threads) { thread.ProcessorAffinity = affinityPtr; } }
2. 有符号整数转换导致掩码解析异常
处理器亲和性掩码是无符号整数,但你使用Convert.ToInt64转换二进制字符串,当掩码的高位(比如32位掩码的第31位)为1时,会被解析为负数,转成IntPtr后系统会识别出错误的掩码。改用Convert.ToUInt64处理无符号掩码,再转换为IntPtr可避免此问题。
3. 变量名拼写错误导致断言失效
代码中Debug.Assert(numCpu <= Environment.ProcessorCount);存在变量名拼写错误(方法参数为numCPU,断言中写为numCpu),这会导致编译错误或断言完全失效,需要修正为一致的变量名。
验证设置有效性
设置完成后,可通过以下代码验证亲和性是否生效:
var currentAffinity = currentProcess.ProcessorAffinity; Console.WriteLine($"当前进程亲和性掩码: {Convert.ToString((long)currentAffinity, 2)}");
内容的提问来源于stack exchange,提问作者Tilman Zuckmantel

