serversim
LearnSimulatorTemplatesPricing
…
…
Open menu
Learn↗Simulator↗Templates↗Pricing↗
My DocumentsDocs
Contents/Chapter 1515 / 16

How we got here — sixty years of evolution

Why were today's methods born in exactly this order? Each one came not from a bright idea but from the bill the previous generation was handed.

Thread pools, event loops and coroutines did not appear because someone had a good idea one morning. Each one exists because of the price the previous generation paid. Processes were heavy, so threads appeared. Threads could not survive the memory bill, so event loops appeared. Event-loop code became unreadable, so coroutines appeared. Step through the timeline below and follow what each era gained and what it paid.

① Every invention was a trade, not progress

Reading this history as "things kept getting better" teaches you nothing. Every step bought something by paying for it elsewhere. Time-sharing bought CPU utilization and paid in context switches. Threads bought lightness and paid in race conditions and locks. Event loops bought memory and paid in callback hell and a hard no-blocking rule. A newer model does not make an older one wrong — it changes what you pay.

② Only three real leaps — isolate, share, register

Sixty years compress into three decisions. (1) Isolate — cut a running program into a unit whose context can be saved and restored, and forbid it from touching anyone else's memory (the process). (2) Share — drop the isolation and put many flows inside one address space (the thread). (3) Register — stop attaching a flow to a wait at all; just register it with the kernel's notification list (the event loop). Everything after that — coroutines, goroutines, virtual threads — is a question of how the third decision is packaged.

③ Coroutines were not a new invention

Here is the striking part: the term "coroutine" was coined in 1963, and Simula 67 shipped them as a language feature. Then they sat at the margins for forty years, because nothing needed that many concurrent things. Only when C10K forced the event loop, and the event loop produced callback hell, did the old idea become the right answer. Technology is not adopted because it is good; it is adopted when a problem demands it.

④ And the code came back to where it started

Code written with virtual threads in 2023 has the same shape as code written on Unix in 1969: straight down the page, blocking calls and all. What changed is underneath — the price of one wait fell from megabytes to kilobytes, and responsibility for the suspension point moved from the kernel to libraries, then to compilers, and now to the runtime. The circle closed, but not in the same place.

⑤ What to watch in the timeline

Step through the timeline and watch the diagram. A box is an address space (a process), a green bar is a kernel-scheduled thread, and a blue dot is a user-level task riding on it. In the 1950s there is one box with one bar; by 1969 the boxes multiply; by 1985 they collapse into one box while the bars multiply. In 1999 the bars stop multiplying, and from 2004 tens of thousands of blue dots pile onto a single bar. Read "what it cost" at every stop — that cost is exactly what called the next era into being.

Step 1/8Speed
1950s

Batch processing — one program at a time

Shape of the concurrent unit

program

▌flow of execution

Who decides the switch
A human (the operator swapping decks)
Cost of one unit· typical
The whole machine — one program owned all of memory
Concurrent units per machine· typical
1

What actually happened

  • 1954IBM 704 — submit a deck, wait for output; the standard workflow
  • 1956GM-NAA I/O — an early batch monitor that chains jobs automatically
  • 1959Channels/DMA spread — devices start moving data without the CPU

The problem it inherited

This is the starting point. A computer cost as much as several houses and was rented by the hour. Yet that expensive CPU spent very little of its time actually computing.

What was invented

You submitted your program as a deck of punched cards, and an operator fed decks into the machine one at a time. Read cards, compute, print, move to the next deck. Exactly one program lived in memory.

What it cost

While the card reader or tape spun, the CPU genuinely did nothing — there was no other program in memory to switch to. Utilization in the single-digit percentages was normal.

What this era left behind →

"Could we give that expensive CPU to another program while this one waits?" That single question produced the next sixty years of inventions.

Related chapters

Chapter 7

Try it in the simulator

  1. 1.Open the 'Evolution Step 1 — MVP' preset: LB -> 3 Spring services (2 each) -> one MySQL. Under bursty load (7,000 RPS average, 21,000 RPS at the peak), the KPI panel's Success is 65.6% (all 29,120 drops are queue-full).
  2. 2.Switch to 'Evolution Step 2' from the library — same load, just a Redis cache and 2 read replicas added — and Success jumps 65.6% -> 100% (though the primary MySQL card's CPU still spikes to 90% at burst peaks, since events are still written synchronously).
  3. 3.Switch again to 'Evolution Step 3': moving only the events onto Kafka keeps Success at 100% while the primary MySQL card's CPU peaks drop from 90% to 61% and p99 peaks from 62.9ms to 59.9ms — the same 'why did the next method appear' story from this chapter, replayed at the architecture level.
Try it in the simulator →

Terms in this chapter

batch processing
Running programs strictly one at a time; only one ever lives in memory.
time-sharing
Holding many programs in memory and slicing CPU time among them. The origin of the process.
C10K
The 1999 challenge of serving 10,000 concurrent connections from one machine — the ceiling of the thread model.
M:N scheduling
M user-level tasks placed by a runtime onto N kernel threads. Goroutines and virtual threads work this way.
carrier thread
The kernel thread that actually runs a virtual thread; on a block, only the rider is swapped.
unmount
The runtime lifting a virtual thread off its carrier; the waiting one is parked, stack and all, on the heap.

Why it evolved this way

The order was set by whichever resource was most expensive at the time, not by taste. In the 1950s and 60s the CPU dominated every other cost, so the answer was to never let it idle (time-sharing). By the 1990s CPUs were cheap but memory and thread stacks were the wall, so the answer was to cut the per-connection cost (event loops). By the 2010s hardware was cheaper than developer time and developer mistakes, so the answer was to keep the performance while making the code readable again (async/await, virtual threads). If you want to guess the next model, ask what is most expensive now.

Every execution model is one era's answer to a single question: who pays for the waiting?

The remaining problem

Finally, let us put all these methods in one table — actually executed, same conditions →

← Previous

Chapter 14. Trap ② — a blocking call on the loop

Next →

Chapter 16. Same work, four methods