Process vs Thread

What is it: A process is an isolated unit of execution with its own address space, file descriptor table, and OS resources (tracked in the Process Control Block). A thread is a unit of execution within a process — threads share the address space and fd table but each has its own stack and register state (Thread Control Block).

graph TD subgraph p1["Process A"] addr1["Address space A<br/>(isolated)"] t1["Thread 1"] --> addr1 t2["Thread 2"] --> addr1 note1["Threads share address space<br/>+ fd table. Own stack + registers."] end subgraph p2["Process B"] addr2["Address space B<br/>(isolated — cannot see A's memory)"] end

Why: Processes provide fault and security isolation — a crash in one process cannot corrupt another's memory. Threads are cheaper and enable shared-memory communication without IPC. Chrome: one process per tab so a buggy page cannot take down the browser. Web servers: threads per connection because shared memory access to caches is fast.

Context switch cost — process: Save all registers → save page table base (CR3 on x86) → flush TLB (virtual-to-physical translations are now invalid) → load new process's registers and page table. TLB flush is the expensive part: every memory access after the switch is a TLB miss until the working set is reloaded. Cost: 1–10 microseconds.

Context switch cost — thread (same process): Save/restore registers and stack pointer. Page table does not change — TLB entries remain valid. 3–5× cheaper than process context switch. Still causes cache thrashing if threads have different data working sets.

Why context switch costs time: Saving and restoring state is fast (~100ns). The real cost is cold caches: after a switch, instruction cache and data cache are filled with the previous task's data. The new task incurs cache misses on its working set until they warm up. TLB invalidation (process switch) amplifies this: every memory access is a cache miss until the page table is re-walked.

Trade-offs: processes — more overhead, better isolation. Threads — cheaper, shared memory, no isolation. Go goroutines — cheaper than threads (~2KB vs ~1MB stack), scheduled in userspace by the Go runtime.

Real-world usage: Proxel uses Node.js Worker Threads for CPU-intensive HTML parsing. The main process manages orchestration; worker threads share an ArrayBuffer for the job queue but are isolated for crash safety.

Common pitfall

Assuming thread-shared memory means "safe to share" is the direct setup for race conditions — sharing an address space makes concurrent access to the same data possible, it doesn't make it correct. Every piece of state shared across threads needs an explicit synchronization decision (mutex, atomic, or "actually don't share it, use a channel instead"); the process/thread boundary only decides whether sharing is possible, not whether it's safe without further work.