CPU affinity is the property of a process or thread that defines the set of logical CPUs on which the scheduler may run it. In Linux process management, affinity is stored as a bit mask per task, and the kernel scheduler never places the task on a CPU outside that mask.
By default every task may run on every online CPU, and the scheduler moves work between cores to balance load. Setting an explicit affinity, often called pinning, trades that flexibility for cache locality, predictable latency and workload isolation, which matters for databases, network processing, real-time systems and virtual machines.
How the scheduler applies the affinity mask
Each task in the kernel carries a `cpus_allowed` mask with one bit per logical CPU. When the Completely Fair Scheduler or its successor EEVDF selects a run queue, and when the load balancer migrates tasks between cores, both consult this mask and skip any CPU whose bit is clear.
Affinity is inherited across `fork()`, so a mask set on a parent shell applies to every program it launches. It applies per thread: in a multithreaded program, each thread has its own mask, and tools that operate on a process ID must be told explicitly to update all threads of the process.
The kernel distinguishes two strengths of affinity. Soft affinity is the scheduler’s natural tendency to keep a task on the CPU where it last ran, because its data is still warm in the cache. Hard affinity is the explicit mask that forbids other CPUs entirely.
Tools and interfaces for setting affinity
Linux exposes CPU affinity through system calls, command-line tools and service settings. Which one to use depends on whether the setting is temporary, part of an application or part of system configuration.
| Interface | Level | Example |
|---|---|---|
| taskset | Command line, existing or new process | taskset -c 2,3 ./server |
| sched_setaffinity() | System call from application code | Called with a cpu_set_t mask |
| pthread_setaffinity_np() | Library call for one thread | Pinning a worker thread |
| CPUAffinity= | systemd unit or system default | CPUAffinity=4-7 |
| numactl | Command line, CPUs and memory nodes | numactl --cpunodebind=0 --membind=0 |
| cpuset cgroup | Group of processes | cpuset.cpus set to 8-15 |
taskset -p shows the mask of a running process in hexadecimal form, so f means CPUs 0 to 3, while taskset -cp shows it as a readable CPU list. To change every thread of a running program, the `-a` option is required, otherwise only the main thread is updated.
CPU affinity compared with related controls
Affinity decides where a task may run, whereas priority and nice values decide how much CPU time it receives. Several neighboring mechanisms in the processes topic are easy to confuse with affinity, and they are often combined in practice.
| Control | What it governs | Scope |
|---|---|---|
| CPU affinity | Which CPUs a task may use | Thread or process |
| Nice value | Relative share of CPU time | Thread or process |
| Real-time priority | Strict preemption order | Thread |
| Cgroup cpuset | Allowed CPUs and memory nodes | Group of processes |
| Cgroup cpu.max | Hard quota of CPU time | Group of processes |
| isolcpus and nohz_full | CPUs removed from general scheduling | Whole system, at boot |
The cpuset cgroup sets an outer limit, and a task’s own affinity mask is effectively intersected with it. A container restricted to CPUs 8 to 15 therefore cannot pin a thread to CPU 2, and container runtimes use exactly this mechanism to implement options such as `–cpuset-cpus`.
When pinning helps and how to plan it
Pinning helps when cache warmth, memory locality or interference dominate performance. It rarely helps general workloads, because the scheduler already balances load well. The following decision table summarizes typical cases.
| Situation | Recommended choice | Reason |
|---|---|---|
| General web or desktop workload | Leave the default | Load balancing outperforms static pinning |
| Multi-socket database server | Bind to one NUMA node with memory | Avoids slow remote memory access |
| Packet processing with DPDK | Pin each poll thread to an isolated core | Removes jitter and context switches |
| Latency-critical real-time task | Pin to cores in isolcpus or nohz_full | Keeps housekeeping work away |
| Virtual machine vCPUs | Pin vCPUs to dedicated host cores | Stable performance and cache reuse |
| Noisy batch jobs | Confine to a subset of cores | Protects interactive services |
Planning requires knowledge of the CPU topology. Tools such as lscpu -e and lstopo show which logical CPUs are hyperthread siblings, which share an L3 cache and which belong to each NUMA node. Pinning two busy threads to sibling hyperthreads of one core often performs worse than leaving them unpinned.
Interrupt affinity complements process affinity. Network card queues can be steered through `/proc/irq/N/smp_affinity`, so that interrupts for a queue are handled on the same core as the thread consuming its packets, which keeps data in the same cache hierarchy.
Risks and common mistakes
Static affinity removes the scheduler’s ability to adapt, so mistakes show up as idle cores next to overloaded ones. The most frequent problems are listed below.
- Pinning too many tasks to one core: they compete for it while other cores stay idle.
- Ignoring hyperthread siblings: two logical CPUs on one physical core share execution units.
- Pinning CPUs without matching memory: on NUMA systems, remote memory erases the cache benefit.
- Assuming stable CPU numbering: numbering differs between machines and can change with firmware settings.
- Forgetting the -a option: only the main thread moves, while worker threads keep the old mask.
Affinity is also not a security boundary on its own. An unprivileged process can change its own mask within its cpuset, so isolation that must hold against the workload itself belongs in cgroups configured by the administrator.
The short version
A modern computer has many processor cores, like a kitchen with many cooks. Normally any cook may pick up any order. CPU affinity is a rule that says this order may only be prepared by these particular cooks.
That can be useful because a cook who keeps working on the same dish already has the ingredients at hand, so the work goes faster. But if too many orders are assigned to the same few cooks while others stand idle, everything slows down. Affinity helps only when it is planned carefully.
Where it makes a difference
A telecom company runs a packet-processing application on a Linux server with two sockets and 32 cores per socket. Throughput is acceptable, but latency spikes of several hundred microseconds appear whenever background services wake up on the same cores as the forwarding threads.
The engineers reserve cores 2 to 15 on the socket attached to the network card with the isolcpus and nohz_full boot parameters. They pin each forwarding thread to one of those cores, steer the card’s interrupts to the same socket and allocate packet buffers on the local NUMA node with numactl. All system services receive CPUAffinity=0-1,16-63 in the systemd configuration.
After the change, latency spikes disappear from the forwarding path, throughput rises because packet data stays in the local cache, and the remaining services run undisturbed on the other cores.