Process vs Thread
A process is the OS’s unit of isolation. It gets its own virtual address space, file descriptor table, and security context. The kernel enforces that no process can read another’s memory without explicit shared-memory setup.
A thread is the unit of execution inside a process. All threads in a process share the same address space and file descriptors — which is both the power and the hazard of threads.
Process A Process B
┌─────────────────────┐ ┌─────────────────────┐
│ Virtual address space│ │ Virtual address space│
│ Thread 1 Thread 2 │ │ Thread 1 │
│ (shared heap/stack)│ │ │
└─────────────────────┘ └─────────────────────┘
↕ kernel enforces boundary ↕
Creating a process (via fork() on Linux) is expensive — the OS copies the page table and process descriptor. Creating a thread is cheap — it shares the parent’s address space and just needs a new stack.
CPU Scheduling
The scheduler decides which thread runs on which CPU core. The key algorithms:
Round-robin: Each thread gets a time slice (typically 1–10ms). Expired? Preempt and move to the back of the queue. Simple, fair, good for interactive workloads.
Completely Fair Scheduler (CFS): Linux’s default. Instead of fixed slices, each thread accumulates “virtual runtime.” The thread with the least virtual runtime runs next. nice values bias the runtime accounting.
Priority scheduling: Real-time threads (SCHED_FIFO, SCHED_RR on Linux) always preempt normal threads. Used for audio drivers, game input loops, anything that can’t tolerate jitter.
Context Switch Cost
A context switch saves the current thread’s registers, program counter, and stack pointer to a kernel structure (the “thread control block”), then restores another’s. On x86-64 this involves:
- Saving general-purpose registers (~100 bytes)
- Switching the page table base register (CR3) — which flushes the TLB if switching processes
- Restoring the new thread’s state
Process context switches are significantly more expensive than thread switches within the same process — the TLB flush is the killer. On a modern CPU, a thread switch within a process costs ~1–5µs; a process switch costs ~10–50µs depending on TLB warm-up.
Practical Implications
Go goroutines are user-space threads scheduled by the Go runtime, not the OS. The runtime multiplexes thousands of goroutines onto a small thread pool — each goroutine’s stack starts at 2KB and grows. The context switch cost is ~100ns vs ~1µs for OS threads.
Node.js uses a single event loop thread backed by libuv’s thread pool for I/O. Concurrency without thread-switch overhead — but blocking the event loop blocks everything.
Java Virtual Threads (JDK 21+) use the same user-space multiplexing model as Go. Thread.ofVirtual().start(...) creates a virtual thread that yields on blocking I/O rather than blocking an OS thread.
What to Know for Interviews
- A process crash cannot corrupt another process’s memory (isolation). A thread crash takes down the whole process.
fork()+exec()is how Unix spawns new programs.fork()copies the process,exec()replaces the image.- Thread-per-request servers (traditional Java EE) hit scalability ceilings because OS thread counts are bounded (~10k). Async/virtual thread models remove this ceiling.