CPU scheduling: how the CPU is shared
Run queues and time slices, priorities and nice values, preemption and why a process stops, real-time scheduling classes, and how to read load average correctly.
Runnable, running, blocked
A thread is runnable when it wants the CPU, running when it has one, and blocked when it is waiting for I/O, a lock or a timer. The scheduler only chooses among runnable threads, so a machine can be busy and idle at the same time.
| State | Meaning | Seen as |
|---|---|---|
| Running | Executing on a core | R in ps |
| Runnable | Waiting for a core | R, competing for the run queue |
| Interruptible sleep | Waiting for I/O or a signal | S in ps |
| Uninterruptible sleep | Waiting on I/O, cannot be interrupted | D in ps — a high count is a red flag |
| Stopped | Suspended by a signal | T in ps |
| Zombie | Exited, not yet reaped | Z in ps |
ps -eo pid,stat,pcpu,pmem,comm --sort=-pcpu | head
# STAT R running S sleeping D uninterruptible
# T stopped Z zombie
uptime
# 10:30:01 up 4 days, 1 user, load average: 8.12, 4.03, 2.00
# the three figures are 1, 5 and 15 minute averagesTime slices, priorities and preemption
- Each thread gets a time slice; when it expires or the thread blocks, the scheduler picks the next runnable thread.
- A preemptive scheduler can take the CPU away mid-instruction-stream, so no thread can monopolise a core.
- The completely fair scheduler on Linux tracks virtual runtime per thread and favours the one that has run least.
- Nice values range from -20 (most favourable) to +19. Each step changes the share of CPU a thread receives when contended.
- CPU affinity pins a thread to a set of cores, which helps cache locality and hurts balance if overused.
nice -n 10 ./batch-job # start at a lower priority
renice -n 5 -p 12345 # change a running process
taskset -c 0-3 ./server # restrict to cores 0 through 3
chrt -f 50 ./realtime-worker # FIFO real-time class, priority 50
# what the running thread is waiting for
cat /proc/12345/status | grep -E "State|voluntary"
cat /proc/12345/sched | head| Class | Behaviour | Risk |
|---|---|---|
| SCHED_OTHER | Fair sharing, default | None |
| SCHED_BATCH | Fair, tuned for throughput | Lower interactivity |
| SCHED_IDLE | Runs only when nothing else wants the CPU | Starves under load |
| SCHED_FIFO | Runs until it blocks or yields | Can hang the machine if it spins |
| SCHED_RR | Round robin within a priority | Still higher priority than any fair thread |
| SCHED_DEADLINE | Reserves CPU bandwidth by deadline | Requires correct parameters or it is rejected |
Reading load average without being misled
Load average counts runnable and uninterruptible threads. On Linux it includes threads blocked in D state, which is why a machine with heavy disk I/O can show a high load while CPU usage is low.
nproc # compare load against the core count
uptime
vmstat 1 5 # r = runnable, b = blocked, then CPU breakdown
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 2 1 0 812345 12000 640000 0 0 12 240 500 1200 15 3 80 2 0
# ^wa = I/O wait- A load of 8 on an 8-core machine is fully busy; the same load on 2 cores is four times oversubscribed.
- High
wainvmstatmeans threads are waiting on I/O, not computing. - A single-threaded bottleneck can show a load near 1 with idle cores everywhere.
- Container CPU quotas change the picture: the host may be idle while your cgroup is throttled.
⚠️
High load and high CPU are not the same problem. Diagnose which of the three you have — runnable threads, uninterruptible threads or I/O wait — before adding capacity, or you will scale the wrong resource and see no improvement.
FAQ
Should I use real-time scheduling in production?
Only for small, well-understood work that must meet a deadline, and only on cores excluded from general work. A real-time thread that spins without blocking can make the machine unreachable.
Why does my process show 400 percent CPU?
It is using four cores. CPU percentages are per core, so a value above 100 means the process is multi-threaded rather than that the measurement is wrong.
Related
Synchronisation: locks, mutexes and semaphores Monitoring and troubleshooting
Last refreshed 2026-09-18.