Course outline · 0% complete

0/28 lessons0%

Course overview →

The scheduler: taking turns very fast

lesson 6-1 · ~12 min · 19/28

From lesson 5-2, the race condition existed because the OS can pause a running thread between any two steps and run something else for a while.

The OS preempts threads and processes whenever it likes, without asking and without the thread noticing.

This lesson is about the machinery that does it, the scheduler. Once it is visible, both the race conditions of unit 5 and the illusion of everything happening at once make sense.

The illusion of everything at once

A laptop has perhaps 8 CPU cores and more than 300 processes, from lesson 3-1. It feels like they all run simultaneously. They do not, and the OS scheduler creates the illusion:

  1. Give a process the CPU for a tiny time slice, a few milliseconds, also called a quantum.
  2. When the slice ends, because a hardware timer fires, pause it by saving its registers and program counter.
  3. Pick the next ready process, restore its saved state, and run it.

Steps 2 and 3 together are a context switch.

QuantityTypical value
time slicea few milliseconds
turns per process per seconddozens
human perception thresholdtens of milliseconds

At a few milliseconds per slice, every process gets dozens of turns per second, far faster than human perception. Music never stutters, the cursor never freezes, and it is all very fast turn-taking.

The simplest fair policy is round-robin: everyone in a circle, one slice each, repeat.

CPU coreruns ONE process at a timebrowserPID 4021musicPID 4022backupPID 4023the gold outline shows who holds the CPU right now
Round-robin scheduling. The CPU visits each process for one time slice, cycling many times per second.

Round-robin, one slice at a time

Each job needs a number of time slices, and the scheduler hands out one at a time in a circle.

jobs = {"browser": 3, "music": 2, "backup": 4}
quantum = 1
timeline = []

while jobs:
    for name in list(jobs):
        timeline.append(name)
        jobs[name] -= quantum
        if jobs[name] <= 0:
            del jobs[name]

print(" ".join(timeline))

Output

browser music backup browser music backup browser backup backup

music needs only 2 slices, so it disappears from the rotation after round two and backup collects the leftover turns.

The list(jobs) call matters more than it looks. Deleting from a dictionary while iterating it directly raises an error, so the loop iterates over a snapshot of the keys, which is exactly how a real scheduler works from a snapshot of the ready queue.

The same jobs with a larger quantum

Each turn now delivers 2 units of work instead of 1.

jobs = {"browser": 3, "music": 2, "backup": 4}
quantum = 2
timeline = []

while jobs:
    for name in list(jobs):
        timeline.append(name)
        jobs[name] -= quantum
        if jobs[name] <= 0:
            del jobs[name]

print(" ".join(timeline))

Output

browser music backup browser backup

Tracing it, browser drops from 3 to 1, music finishes at 0, and backup drops from 4 to 2. Browser then finishes, and backup finishes last.

QuantumTurns takenTrade-off
19responsive, more switching
25less switching, longer waits

Bigger slices mean fewer context switches but longer waits for everyone else, which is a real OS tuning trade-off rather than an artifact of this simulation.

One core running two programs at once

A single-core machine running a music player and a compiler with no glitches is really alternating them in millisecond slices, faster than human perception.

One core executes exactly one instruction stream at a time, so concurrency on one core is rapid turn-taking rather than simultaneity.

TermMeans
concurrencymany tasks in progress, interleaved
parallelismmany tasks executing at the same instant

With multiple cores you get true parallelism on top of the turn-taking, but the illusion technique is unchanged. The scheduler still slices time on each core, which is why a machine with 8 cores can comfortably run 300 processes.