taskset: Setting CPU Affinity in Linux

taskset: Setting CPU Affinity in Linux

What You'll Learn

  • How to pin a process to specific CPU cores with taskset
  • The difference between launch-time pinning (-c) and reassigning a running process (-p)
  • Practical patterns for benchmarking, core isolation, and NUMA

Quick Summary

  • Pin at launchtaskset -c 0,1 ./program
  • Pin a running processtaskset -cp 0-3 <PID>
  • Check current statetaskset -cp <PID>
  • The list form (-c) is readable; the hex bitmask suits automation

Prerequisites

  • taskset ships with the util-linux package (preinstalled on most distros)
  • Core numbers start at 0. Count them with nproc
  • Changing a running process needs ownership of that process (or root)

What is CPU affinity?

Conclusion: CPU affinity restricts which CPU cores a process or thread is allowed to run on. taskset is the command to view and change that setting.

By default the Linux scheduler is free to move a process to any idle core. Setting CPU affinity means the process can only run on the cores you specify.

The main reasons to pin are:

  • Cache locality: avoid L1/L2 cache invalidation caused by core migration
  • Core isolation: give a latency-sensitive process a dedicated core, free of interference
  • Reproducible benchmarks: run on the same cores every time to reduce measurement noise

Affinity specifies a set of allowed cores. Narrow it to one core for hard pinning, or allow several and let the scheduler pick within that set.

Check core count and numbering

Conclusion: Before deciding where to pin, confirm the available core count with nproc. Core numbers start at 0.

$ nproc
8

If it prints 8, valid core numbers are 07. Specifying a nonexistent core like taskset -c 8 ... returns an error.

Use lscpu when you also need the physical/logical layout.

$ lscpu

Pin cores at launch (-c)

Conclusion: To pin as you start a command, use taskset -c <core-list> <command>. The list form accepts 0,2 (individual) and 0-3 (range).

The basic form: start a new process pinned to specific cores from the very beginning.

# Launch program on cores 0 and 1 only
$ taskset -c 0,1 ./program

# Launch on the range of cores 0 through 3
$ taskset -c 0-3 ./program

# Hard-pin to a single core (core 2)
$ taskset -c 2 ./program

-c (--cpu-list) lets you list core numbers directly, so it is easy to read. Use , for individual cores, - for ranges, and mix both (for example 0,2,4-7).

Pass options to the launched command by appending them as usual. taskset -c 0,1 stress-ng --cpu 2 works with no -- separator needed.

View and change a running process (-p)

Conclusion: For an already-running process, use -p (--pid): taskset -cp <PID> to check, taskset -cp <core-list> <PID> to change.

Check the current affinity

$ taskset -cp 1234
pid 1234's current affinity list: 0-7

0-7 means "may run on all cores" (the default). With -c you get the list form; without it, a hex mask.

Reassign a running process

# Reassign PID 1234 to cores 0 and 1
$ taskset -cp 0,1 1234
pid 1234's current affinity list: 0-7
pid 1234's new affinity list: 0,1

Find the PID with ps or pgrep.

$ pgrep -f program

Mind the argument order

In -p mode the core list comes first, the PID second: taskset -cp 0,1 1234, not taskset -cp 1234 0,1. Reversing them makes taskset treat the PID as a mask and fail.

Hex bitmask vs list form

Conclusion: With -c you specify a core list; without it, a hex bitmask. In the mask, each bit maps to one core (bit 0 = core 0).

Natively, taskset specifies cores as a hexadecimal bitmask. -c is the option that swaps that for a human-readable list.

Target cores List form (-c) Hex mask
Core 0 0 0x1
Cores 0,1 0,1 0x3
Core 1 only 1 0x2
Cores 0–3 0-3 0xf
Core 3 only 3 0x8

The mask is easiest to read in binary. The least significant bit (rightmost) is core 0. 0x3 is 0b0011 (cores 0 and 1); 0x8 is 0b1000 (core 3).

# Launch with a mask (cores 0,1 = 0x3)
$ taskset 0x3 ./program

# Check with a mask (no -c)
$ taskset -p 1234
pid 1234's current affinity mask: ff

For hand-typed commands the list form (-c) is safer. A single wrong hex digit points at a different core. Reach for the mask only when a script generates it.

Apply to all threads (-a)

Conclusion: To pin every thread of a multithreaded process, add -a (--all-tasks). Without it, only the main thread is affected.

When operating on a running process with -p, by default affinity applies only to the given PID (the main thread) and does not propagate to existing child threads. Use -a to pin all threads in the process at once.

# Pin all threads of PID 1234 to cores 0-3
$ taskset -acp 0-3 1234

Even with -a, threads created after the change inherit the process affinity. -a applies to existing threads; future threads inherit anyway. Do not conflate the two.

Real-world use cases

Conclusion: The classic cases are reproducible benchmarking, isolating latency-sensitive processes, and keeping memory access local on NUMA.

Make benchmarks reproducible

Measuring on the same cores every time removes the cache-miss variance caused by core migration.

$ taskset -c 2,3 ./benchmark

Isolate latency-sensitive processes

Combine with the kernel boot parameter isolcpus, which removes cores from the OS scheduler, then place only your target process on them with taskset.

Keep memory local on NUMA

On NUMA systems, pinning to cores on the same node as the memory avoids remote memory access. When you also need to control memory allocation, numactl fits better (taskset only handles CPU placement).

The over-pinning trap

Pin too aggressively and a process cannot use other idle cores, which can make it slower. Pin only after measuring and confirming the benefit. Do not pin blindly.

Common errors and fixes

Conclusion: Most are a nonexistent core number, insufficient permissions, or wrong argument order.

sched_setaffinity: Invalid argument

You specified a core number that does not exist. Check the range with nproc.

# Only 8 cores (0-7) exist but core 8 was requested → error
$ taskset -c 8 ./program

Operation not permitted

You are trying to change another user's process. Run as the process owner or use sudo.

$ sudo taskset -cp 0,1 1234

command not found: taskset

util-linux is missing (for example a minimal container). On Debian/Ubuntu, install it:

$ sudo apt install util-linux

Next Reading