Exercise 2: "One Turn" Is Not a Fair Unit of CPU Time — Possible Solution ==================================================================== THE TEST ------------------------------ def program_light(): for i in range(3): yield i # a trivial, near-instant step def program_heavy(): for i in range(3): total = 0 for _ in range(200_000): # real, substantial work inside ONE step total += 1 yield total light_gen, heavy_gen = program_light(), program_heavy() light_total_time = heavy_total_time = 0.0 for _ in range(3): t0 = time.perf_counter(); next(light_gen); light_total_time += time.perf_counter() - t0 t0 = time.perf_counter(); next(heavy_gen); heavy_total_time += time.perf_counter() - t0 RESULT ------------------------------ program_light's own 3 steps took: 0.014ms total program_heavy's own 3 steps took: 13.714ms total program_heavy consumed roughly 1,016x more real wall-clock time than program_light, even though both programs received the exact same number of turns -- three each -- in the manual round-robin. WHY COUNTING TURNS ISN'T THE SAME AS SHARING TIME ------------------------------ The manual interleaving loop from Finding 3 (and this exercise's own loop) has no concept of HOW LONG a call to next() takes -- it simply calls it, waits for it to return, and moves on to the next program. Nothing measures or limits the real work program_heavy does inside its own single yield-to-yield stretch. A program is free to do an enormous amount of computation between two yield points, and the round-robin loop has no way to notice or intervene until that program voluntarily yields control back. WHY THIS IS A GENUINE FAIRNESS PROBLEM, NOT JUST A TIMING CURIOSITY ------------------------------ If program_light and program_heavy were two real, unrelated programs sharing one CPU, a user running program_light would experience real, noticeable unfairness: program_heavy's own single turn could occupy the CPU for a length of time completely disproportionate to program_light's own turn, even though the SCHEDULER'S own bookkeeping (three turns each) looks perfectly fair on paper. "Number of turns" and "amount of CPU time actually received" are two different quantities, and a scheduler that only tracks the first one is measuring the wrong thing. WHY THIS WORKS AS AN ANSWER ------------------------------ Measuring real wall-clock time per turn, rather than just trusting that equal turn-counts imply equal treatment, exposes a genuine, quantified unfairness (over 1,000x) hiding behind an apparently fair turn-based scheme -- and points directly at why Chapter 8's own timer-interrupt-based preemption exists: it bounds how much real time a single turn can consume, rather than trusting a program to yield control back in a timely and fair way on its own.