Processes vs. Threads — Everything Follows from Shared Memory

4 min read

An interview staple — and a working concept you run into daily in server configs: nginx’s worker_processes, gunicorn’s --workers and --threads, Java’s thread pools all sit on it. Knowing the process/thread difference ultimately means knowing what those knobs change. The root of the difference is one thing: is memory shared? This post derives everything else from that single criterion.

Definitions — the unit of execution, and the flows inside it #

A process is one running program and the unit to which the OS allocates resources. Each process owns its private virtual memory space (code, heap, stack), file descriptors, and credentials. Processes cannot see or touch each other’s memory — the kernel and hardware (MMU) enforce the isolation.

A thread is a flow of execution inside a process. Threads of the same process share its code, heap, and file descriptors, keeping only their own stack and registers. The kernel scheduler puts threads (not processes) on CPUs, so the summary holds: threads are the unit of execution, processes the unit of resources.

Three practical consequences of sharing #

First, communication cost — threads just read and write the same heap variables. Fast, but the instant two touch the same data you need locks, and all of multithreading’s pain (deadlocks, races) flows from that sharing. Processes, memory-isolated, must use explicit IPC — pipes, sockets, shared memory segments. Slower and clumsier, but there is simply no way to accidentally trample each other’s data.

Second, creation and switching cost — creating a process means standing up a memory space (Linux’s trick for making that cheap is the next post’s copy-on-write); creating a thread means adding a stack. Context switches split the same way: switching between threads of one process keeps the memory space, so it’s relatively cheap; switching between processes changes address spaces and pays the cache/TLB penalty on top. The excessive-context-switch story from Why Your Server Is Slow #1 is exactly this cost.

Third, blast radius — one thread corrupting memory kills the whole process: a shared heap means one thread’s corruption is everyone’s. Processes die alone; neighbors keep running. This property anchors the design choices below.

What real software chose #

Real systems teach the tradeoff faster than textbook tables.

  • nginx: multiple worker processes. A dead worker doesn’t kill the service, and workers share little state — isolation’s benefits at nearly no cost.
  • Web browsers: a process per tab. Choosing the expensive option so one tab’s death spares the browser, plus memory isolation between sites (security).
  • Java and Go servers: one process, many threads (goroutines). Requests need to share connection pools and caches, so shared memory pays, and these runtimes ship strong concurrency tooling.
  • Python web servers (gunicorn and friends): a process × thread hybrid. CPython’s GIL keeps one process’s threads from running CPU work in parallel, so CPU utilization comes from process count and I/O concurrency from threads. The GIL’s full story is in Modern Python Advanced #5.

That table doubles as a decoder for worker settings: processes buy isolation and CPU parallelism, threads buy shared state and I/O concurrency. Read framework defaults (processes by core count, threads by I/O share) against those axes and the reasoning is visible.

Two common confusions #

  • “Threads are always faster” — creation and switching are cheaper; execution isn’t. Compute runs at the same speed, and under heavy lock contention multithreading can lose to multiprocessing.
  • Concurrency ≠ threads — threads are one means among several. Async I/O (event loops) handles tens of thousands of connections on one thread — the Python asyncio and Node.js model. “Lots of connections” doesn’t mean “more threads”; whether the work is CPU-bound or I/O-wait-bound comes first.

Summary #

  • The root difference is memory sharing: processes are isolated resource units, threads are execution flows sharing memory inside one.
  • Sharing makes communication cheap at the price of locks and races; isolation makes communication costly but shrinks the blast radius.
  • Threads are cheaper to create and switch — not faster to run.
  • Real-world choices (nginx’s processes, Java’s threads, Python’s hybrid) all answer the same two questions: what state must be shared, and what may die together?
  • Read worker knobs as: process count = isolation and CPU parallelism, thread count = I/O concurrency.

Next: the moment a process is born — how fork makes expensive process creation cheap, via copy-on-write.

X