4. CPU亲和性概念:硬亲和性与软亲和性的定义与区别

CPU亲和性(CPU Affinity)是控制进程/线程在哪些CPU核心上运行的核心机制。在集群节点资源竞争场景下,正确理解并配置CPU亲和性,是消除缓存抖动、减少跨片访存延迟、实现性能可预测性的关键手段。根据绑定生效的层级与强制程度,CPU亲和性分为硬亲和性和软亲和性两类。

4.1 硬亲和性(Hard Affinity)

硬亲和性通过系统调用 sched_setaffinity / sched_getaffinity 将进程或线程绑定到指定的CPU核心集合。该绑定对内核调度器具有强制约束力:调度器在唤醒、迁移、负载均衡时,必须将任务限制在用户指定的CPU掩码内,不得越界。

  • 作用粒度:进程/线程级(task_struct)。
  • 生效方式:用户态程序主动调用系统API,或通过 taskset 命令行工具设置。
  • 持久性:绑定掩码随进程继承,子进程默认继承父进程的亲和性掩码。
  • 典型用途:实时任务隔离、DPDK轮询线程绑定、数据库关键线程锁定专用核心。
关键API示例:
#include <sched.h>
#include <unistd.h>

cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(2, &mask);   // 绑定到CPU 2
CPU_SET(3, &mask);   // 绑定到CPU 3

pid_t pid = getpid();
if (sched_setaffinity(pid, sizeof(mask), &mask) == -1) {
    perror("sched_setaffinity");
    return -1;
}

4.2 软亲和性(Soft Affinity / cpuset)

软亲和性通过 cpuset 子系统(cgroup v1 或 cgroup v2 的 cpuset 控制器)实现。它定义了一组进程可以使用的CPU核心集合,但不强制调度器将进程固定在某个核心上;调度器在cpuset允许的范围内仍可自由迁移进程,以实现负载均衡。

  • 作用粒度:cgroup组级(一组进程共享同一个cpuset)。
  • 生效方式:通过cgroup文件系统写入 cpuset.cpus 和 cpuset.mems。
  • 约束性质:软约束——进程只能在cpuset指定的核心上运行,但调度器可在该集合内任意迁移。
  • 典型用途:容器CPU资源限制、NUMA节点绑定、分区调度。
实践要点:cpuset 通常与 cpuset.mems 配合使用,将进程的内存分配限制在指定的NUMA节点上,实现CPU和内存的局部性绑定。

4.3 核心区别对比

对比维度 硬亲和性(sched_setaffinity) 软亲和性(cpuset)
绑定粒度 单个进程/线程 cgroup组(一组进程)
强制程度 强制:调度器不得越界 软约束:调度器可在集合内迁移
设置接口 系统调用(sched_setaffinity) cgroup文件系统(cpuset.cpus)
继承性 子进程继承父进程掩码 子进程加入父cgroup,继承cpuset
负载均衡 禁止跨掩码迁移,可能造成负载不均 允许集合内迁移,调度器可做均衡
NUMA感知 仅绑定CPU,不控制内存节点 可同时绑定CPU和内存节点(cpuset.mems)
典型场景 实时线程、DPDK、低延迟应用 容器分区、数据库实例隔离、批处理作业

4.4 两种亲和性的协同关系

在实际集群节点中,硬亲和性与软亲和性可以同时生效,且存在层级叠加关系:

  1. cpuset 划定边界:首先通过cpuset将一组进程限制在某个CPU子集(如NUMA节点0的4个核心)。
  2. sched_setaffinity 做最终锁定:在cpuset允许的范围内,关键线程再调用硬亲和性绑定到具体某个核心。
  3. 调度器行为:最终生效的CPU掩码 = 进程的硬亲和性掩码 ∩ cpuset允许的CPU集合。若交集为空,系统调用返回错误(EINVAL)。
常见陷阱:当cpuset缩小了允许的CPU集合后,已设置硬亲和性的进程如果掩码与新的cpuset无交集,进程会被强制迁移到cpuset中的其他核心,硬亲和性实际上被覆盖。因此,修改cpuset时必须检查所有受影响的进程亲和性掩码。

4.5 亲和性层级关系图

下图展示了从系统全局到单个线程的CPU亲和性层级结构,以及硬亲和性与软亲和性在其中的位置:

CPU亲和性层级关系与协同机制 系统所有CPU核心(0 ~ N-1) cpuset 软亲和性(cgroup边界) 允许的CPU子集:如 CPU 2-5(NUMA节点0) 进程组(cgroup内的多个进程) 进程A(无硬亲和性) 进程B(硬亲和性:CPU 3) 进程C(硬亲和性:CPU 2,4) 内核调度器:最终CPU掩码 = 硬亲和性 ∩ cpuset 进程A可在CPU 2-5任意迁移 | 进程B固定CPU 3 | 进程C可在CPU 2,4迁移 说明: 硬亲和性在cpuset边界内生效;若硬亲和性掩码超出cpuset范围,调度器自动忽略越界部分。 cpuset缩小后,原硬亲和性可能被静默覆盖,需通过 /proc/<pid>/status 中的 Cpus_allowed 字段确认实际掩码。

4.6 实际场景选择建议

场景 推荐方案 理由
DPDK / 实时音频处理 硬亲和性(sched_setaffinity) 需要避免核心迁移,保证缓存命中率和延迟确定性
容器化微服务 软亲和性(cpuset) 容器组共享CPU池,允许调度器做轻量负载均衡
数据库关键线程 + 普通线程混合 cpuset 划定分区 + 关键线程硬亲和性 分区隔离保证关键线程独占核心,普通线程在剩余核心上弹性调度
NUMA感知批处理作业 cpuset 绑定NUMA节点 + 硬亲和性绑定具体核心 避免跨片访存,同时消除同片内核心迁移带来的L3抖动
性能校准提示:在集群节点上做性能校准前,应先用 lscpu -e 查看CPU拓扑,再用 taskset -p <pid> 或 cat /proc/<pid>/status | grep Cpus_allowed 确认当前亲和性配置。校准过程中,建议先使用cpuset划定测试边界,再对热点线程施加硬亲和性,分步验证性能变化。