Frontend Labs
⋯

Chapter 8 — Scheduling: who gets the CPU, and for how long

In one sentence

With more runnable processes/threads than CPU cores (almost always true), the kernel's scheduler decides, many times per second, which one runs next and for how long — and the fairness and responsiveness of your entire machine depends on how good that decision is.

Why this matters

"Why does my high-priority job feel slow" and "why did this container get throttled" both trace back to scheduling decisions — and nice/cgroup CPU limits (Chapter 43) are literally you giving the scheduler instructions.

How it actually works

   THE BASIC PROBLEM: you might have 4 CPU cores and 200 runnable
   processes. The scheduler has to pick which handful run RIGHT NOW, and
   for how long, before switching to the next batch -- fairly enough that
   nothing starves, responsively enough that interactive programs (your
   mouse cursor, your SSH session) don't feel laggy, and efficiently
   enough that the switching itself (Chapter 9) doesn't eat all your
   CPU.

   LINUX'S SCHEDULER (CFS -- the Completely Fair Scheduler, the default
   since 2007, itself succeeded by EEVDF in newer kernels) works, in
   spirit, like this:
     - every runnable task accumulates "virtual runtime" while it runs
     - the scheduler always picks the task with the LEAST virtual
       runtime to run next -- i.e., whoever has had the LEAST CPU time
       so far, relatively speaking, goes next.
     - this naturally converges toward fairness: no task can hog the CPU
       indefinitely, because the more it runs, the further behind
       (in scheduling priority) it falls relative to everyone else.

   NICE VALUES let you bias this, without breaking fairness entirely:
     `nice -n 10 mycommand`    lower priority (be nice to others,
                              range -20 to +19, higher number = lower
                              priority)
     `nice -n -10 mycommand`   higher priority (needs privilege)
   This doesn't GUARANTEE a task runs sooner -- it changes the WEIGHT the
   scheduler uses when deciding, so a "niced-up" process accumulates
   virtual runtime slower, and gets picked more often, relatively.

Real-world example

   The move FROM Linux's older "O(1) scheduler" TO the Completely Fair
   Scheduler (CFS) in 2007 (kernel 2.6.23, engineered by Ingo Molnar) is
   a well-documented real case study in why scheduling fairness matters
   at scale. The O(1) scheduler's heuristics for distinguishing
   "interactive" from "CPU-hungry" tasks were complex, hard to tune, and
   produced noticeably unfair or laggy behaviour under certain real
   workloads -- users and kernel developers reported cases where
   interactive tasks (things you'd expect to feel snappy, like typing in
   a terminal) got starved by batch workloads. CFS replaced the
   heuristic-heavy approach with the much simpler "always run whoever
   has had the least CPU time so far" fairness principle described
   above, and became the long-standing Linux default specifically
   because it behaved more predictably and fairly across a huge range of
   real workloads without needing per-workload tuning.

Try it yourself

# Watch nice values change real scheduling behaviour
nice -n 19 yes > /dev/null &     # low priority CPU hog
yes > /dev/null &                # normal priority CPU hog
top    # compare %CPU for the two `yes` processes; the niced one should
       # get noticeably less CPU time on a busy system. Ctrl-C both after.
kill %1 %2

# See the current nice value of every process
ps -eo pid,ni,cmd | head -20

Build it in Go

A container's CPU limit is a CFS quota, not fewer CPUs, so programs that size thread pools by CPU count get throttled. Go 1.25+ reads the cgroup limit and sets GOMAXPROCS to match. Chapter 75 measures the difference: forcing GOMAXPROCS=8 under a 1.5-CPU limit cost 13.7 s of throttling in 2 s.

Common mistakes

  • Thinking nice guarantees priority. It's a WEIGHT/bias for the scheduler's fairness algorithm, not a hard priority override — a heavily niced-down process still gets SOME CPU time, just less.
  • Assuming more CPU cores means the scheduler matters less. With enough runnable tasks (very common on a busy server or a laptop with many background apps), scheduling decisions matter at any core count.
  • Confusing nice (CPU scheduling priority) with ionice(I/O scheduling priority) or cgroup CPU limits (Chapter 43, a hard cap, not just a bias).

Check yourself

  1. In one sentence, what problem is the scheduler solving?
  2. Roughly how does Linux's Completely Fair Scheduler decide what runs next?
  3. Does a negative nice value guarantee a process runs immediately? Why or why not?

Further reading

  • Book (free): OSTEP, the scheduling chapters (MLFQ and CFS) — the clearest conceptual walkthrough available.
  • Documentation: the Linux kernel's own Documentation/scheduler/ directory (sched-design-CFS.rst) — written by CFS's own author.
  • Reference: man 1 nice, man 2 sched_setscheduler.

Generated from real-life-os-guide.md · 8 Oct 2026 16:36