Frontend Labs
⋯

Chapter 7 — Threads: sharing everything except the stack

In one sentence

A thread is a separate, independently-scheduled sequence of execution that lives INSIDE a process and shares that process's memory with every other thread in it — which makes threads cheaper than processes and dramatically easier to get wrong.

Why this matters

"Thread-safe" and "race condition" (Chapter 17) are two of the most common words in any serious engineering discussion, and they only make sense once you know exactly what threads do and don't share.

How it actually works

   A PROCESS can contain ONE OR MORE threads. All threads in the same
   process share:
     - the same memory (global variables, heap allocations)
     - the same open file descriptors
     - the same process ID (from the OS's perspective, mostly)

   Each thread gets its OWN:
     - stack (local variables, function call history)
     - CPU register state / program counter (where THIS thread currently
       is in the code)
     - thread ID

   WHY THIS MATTERS SO MUCH: because threads share memory, TWO THREADS
   CAN TOUCH THE SAME VARIABLE AT THE SAME TIME -- something two
   separate PROCESSES simply cannot do by accident, because their
   memory is isolated (Chapter 10). This is BOTH the main reason threads
   are useful (fast communication, no copying needed, no fork() cost)
   AND the main reason threaded code is notoriously hard to get right
   (Chapter 17's race conditions, Chapter 18's locks).

   THREADS VS. PROCESSES, the trade-off in one table:
     PROCESSES   isolated (safer), heavier to create, communicate via
                slower IPC mechanisms (Chapter 19)
     THREADS     share memory (faster, riskier), cheap to create, need
                explicit synchronisation (Chapter 18) to be safe

Real-world example

   Node.js was DELIBERATELY built single-threaded for its JavaScript
   execution (I/O is handled separately, under the hood, via a thread
   pool and an event loop) -- a choice its creator, Ryan Dahl, explained
   publicly in his original 2009 talks introducing the project. The
   explicit reasoning: multi-threaded programming with shared mutable
   state is HARD TO GET RIGHT (exactly this chapter's warning), and for
   I/O-heavy server workloads, an event-driven single-threaded model
   sidesteps an entire category of race-condition and locking bugs
   almost entirely, at the cost of needing a different mental model
   (callbacks/promises/async-await) for writing non-blocking code.
   This is a genuinely influential, still-debated design choice
   (Debated: many engineers argue Node's model trades one kind of
   complexity for another, "callback hell" or now `async`/`await`
   chains) -- but it's a real, deliberate illustration of "threads are
   powerful but dangerous enough that entire platforms are designed
   specifically to minimise their use."

Try it yourself

# See threads vs processes on your own machine right now
ps -eLf | wc -l     # count of THREADS across the whole system
ps aux | wc -l       # count of PROCESSES

# Pick one multi-threaded process and see its thread count
pgrep -f firefox 2>/dev/null | head -1 | xargs -I{} cat /proc/{}/status 2>/dev/null | grep Threads
# or, if you don't have firefox running, try:
cat /proc/self/status | grep Threads   # your shell has how many?

Build it in Go

Goroutines are Go's answer to "threads are expensive". In a measured run, 20,000 goroutines (sleeping or blocked on sockets) used 16 OS threads, while 100 goroutines blocked in raw read(2) calls pushed it to 108. See Chapter 73 for the lab, and the Go guide's scheduler chapter for how the runtime maps goroutines onto threads.

Common mistakes

  • "Threads are always faster than processes." For CPU-bound, isolated work, processes (which can run on separate cores, same as threads can) are often simpler and just as fast — threads win specifically when you need fast, frequent shared-memory communication.
  • Assuming shared memory means synchronised access. The OS guarantees threads CAN see the same memory; it guarantees NOTHING about the order or safety of concurrent access — that's entirely on you and your locks (Chapter 18).
  • Thinking "single-threaded" means "can't handle concurrency." Node.js handles huge numbers of CONCURRENT connections single-threadedly, via the event loop — concurrency and threading are related but distinct ideas (§0.4).

Check yourself

  1. What do threads in the same process share, and what does each thread keep private?
  2. Why are threads cheaper to create than processes?
  3. Why did Node.js's creator choose a single-threaded model for JavaScript execution?

Further reading

  • Book (free): OSTEP, "Concurrency" chapters (the introduction through locks) — the best treatment of why threading is hard, and how to do it safely.
  • Talk: Ryan Dahl's original Node.js introduction talk (2009, JSConf) — the actual reasoning, from the source.
  • Reference: man 7 pthreads — the POSIX threads API reference.

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