CPU Scheduling

Your machine runs hundreds of processes on a handful of CPU cores, and yet everything feels like it's running at once. That illusion is the CPU scheduler's doing — rapidly switching the cores between processes, dozens of times a second, deciding who runs and for how long. Understanding scheduling explains why your program isn't always running, why context switches cost, and why "add more threads" doesn't always mean faster.

The process post showed processes cycling through Ready/Running/Blocked; the threads post showed many threads wanting to run. But there are only so many CPU cores. The scheduler is the OS component that decides which ready process/thread runs on a core, when, and for how long — creating the illusion that many programs run simultaneously on limited hardware. This post covers how scheduling works, context switching and its cost, scheduling policies, and why this matters for your programs’ performance.

The illusion of simultaneity

Your computer has, say, 8 CPU cores but runs hundreds of processes and threads — far more than 8 can run at literally the same instant. Yet everything appears to run concurrently. The scheduler creates this illusion through time-sharing: it rapidly switches each core between different ready processes, giving each a small slice of CPU time (a time slice or quantum, often milliseconds) before switching to the next. Because the switching is so fast, each program appears to run continuously, when really it’s getting frequent small turns:

1 core, 3 processes wanting to run:
  time →  [A][B][C][A][B][C][A][B][C]...   (each gets ~milliseconds, rapidly rotated)
  → to a human, A, B, and C all seem to run "at the same time"

This is concurrency (many things making progress by interleaving) versus parallelism (many things literally running at once — which needs multiple cores). With 8 cores you get real parallelism (8 truly simultaneous) plus time-sharing on each core (hundreds interleaved). The scheduler manages this: it picks which ready processes run on the available cores and rotates them. This is the resource-management job (post one) applied to the CPU — sharing a scarce resource (cores) among many competitors (processes/threads) to give everyone progress.

Preemption and context switching

The scheduler’s power comes from preemption — the ability to interrupt a running process and switch to another, even if the running process didn’t voluntarily yield. Modern OSes are preemptive: a timer interrupt fires periodically, the scheduler regains control, and it can switch to a different process. This is essential — it means one process can’t monopolize the CPU (the scheduler will preempt it after its time slice), so the machine stays responsive and fair.

Switching from one process to another is a context switch, and it’s not free:

This overhead has real consequences: too much context switching wastes CPU on switching rather than working. If you have far more active threads than cores, the scheduler thrashes between them, and context-switch overhead dominates — which is why “more threads” doesn’t always mean faster (a common misconception). Beyond a point, adding threads adds context-switching cost without adding parallelism (you only have so many cores), so performance degrades. Understanding context-switch cost explains this and guides sizing thread/worker pools to roughly match cores for CPU-bound work. (This connects to why async I/O — a later post — can outperform many threads: it avoids the per-connection thread and its context-switch overhead.)

Scheduling policies

Which ready process should the scheduler pick? That’s the scheduling policy, and it balances competing goals — no single policy is best for everything:

These goals conflict (maximizing throughput might starve interactive tasks; perfect fairness might hurt responsiveness), so real schedulers make trade-offs. Common ideas:

The practical point for engineers: the scheduler is trying to balance fairness, responsiveness, and throughput across everything on the machine, using priorities and time slices — and you can influence it (process priority/nice values, CPU affinity, real-time priorities for special cases). But mostly, understanding that the scheduler exists and rotates processes explains system behavior: why your process shares the CPU, why priority matters, and why a busy machine slows everything.

Why scheduling matters for your programs

Scheduling isn’t abstract — it shapes your applications’ performance:

The scheduler is the OS creating the illusion of simultaneity by rapidly, preemptively rotating processes across cores — balancing fairness, responsiveness, and throughput — and its behavior (time-slicing, context-switch cost, policies) directly explains your programs’ performance characteristics. The next post moves to the other great resource the OS manages and abstracts: memory.

Key takeaways

Further reading

Sources & References