Core Concepts
When you run a program, the operating system creates an instance of execution. But is it a process or a thread? Understanding the distinction is critical for building high-performance systems.
| Feature | Process | Thread |
|---|---|---|
| Definition | Independent program execution unit | Lightweight unit within a process |
| Memory | Isolated memory space | Shared memory (heap, code, globals) |
| Communication | IPC (pipes, sockets, shm) | Shared variables, locking |
| Context Switch | May change address-space state; hardware and workload matter | Same-process switches retain the address space but still have scheduling/cache costs |
| Failure | Isolated (crash doesn't affect others) | Crash kills entire process |
Memory Architecture
The key difference lies in how memory is organized.
Process Layout
Each process has its own virtual address space.
Separate virtual address spaces
Each process has its own private memory map. Matching regions in the two processes are separate allocations, not shared memory.
Process A
Private address spaceProcess B
Private address spaceOS kernel space
The operating system manages processes and protected kernel memory; application code cannot directly access kernel space.
Thread Layout
Threads live inside a process and share resources.
One address space, shared process resources
Threads belong to one process. They can access its shared code, heap and global data, while keeping separate stacks and register state.
Single process address space
SharedThread 1
PrivateStack
Calls and local execution state
Registers
Current CPU state
Thread 2
PrivateStack
Calls and local execution state
Registers
Current CPU state
Note: Threads share the heap but have their own stacks. A local variable can still reference a shared object, and a pointer to stack data can escape. Separate stacks do not make every operation on local variables thread-safe.
Context Switching Cost
Switching between execution units isn't free. The CPU must save state and load new state.
Process Context Switch (Heavy)
- Save registers
- Save stack pointer
- Switch Page Table Directory (Virtual Memory) 🚨
- Update translation state as required; address-space identifiers can avoid a full TLB flush
- Load new state
Thread Context Switch (Light)
- Save registers
- Save stack pointer
- Load new state (No memory map change, No TLB flush)
Code Examples
Python: Multiprocessing vs Threading
The usual CPython build uses a Global Interpreter Lock (GIL) for Python bytecode. Optional free-threaded builds and native code that releases the GIL behave differently.
import time
import threading
import multiprocessing
def cpu_bound_task():
count = 0
while count < 100_000_000:
count += 1
def run_threading():
start = time.time()
t1 = threading.Thread(target=cpu_bound_task)
t2 = threading.Thread(target=cpu_bound_task)
t1.start(); t2.start()
t1.join(); t2.join()
print(f"Threading: {time.time() - start:.2f}s")
# Measure on your interpreter; ordinary GIL-enabled CPython serializes this bytecode work.
def run_multiprocessing():
start = time.time()
p1 = multiprocessing.Process(target=cpu_bound_task)
p2 = multiprocessing.Process(target=cpu_bound_task)
p1.start(); p2.start()
p1.join(); p2.join()
print(f"Multiprocessing: {time.time() - start:.2f}s")
# Processes can execute concurrently on multiple cores; startup and IPC have costs.
if __name__ == "__main__":
run_threading()
run_multiprocessing()
Golang: Goroutines (User-Space Threads)
Go uses "Goroutines" which are M:N mapped (M goroutines on N OS threads).
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
start := time.Now()
// Launch 100,000 lightweight threads
for i := 0; i < 100000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// Tiny memory footprint (~2KB)
// Fast context switch in user space
}()
}
wg.Wait()
fmt.Printf("Spawned 100k goroutines in %v\n", time.Since(start))
}
Real-World Scaling Decisions
Chrome: Multi-Process Architecture
Chromium uses multiple processes with site isolation; tabs, frames, workers, and process reuse do not map to exactly one process per tab.
- Pros: If one tab crashes (segfault), the browser stays alive. Security isolation (sandboxing).
- Cons: High memory usage (each tab needs its own memory overhead).
Redis: Single-Threaded Event Loop
Redis uses a single thread for command processing.
- Why?: Reduces per-command synchronization and per-connection thread overhead; it does not eliminate context switches.
- Scale: Used I/O multiplexing to handle thousands of connections efficiently.
Apache vs Nginx
- Apache (Prefork): A pool of worker processes, with a process handling one connection at a time. Stable but memory hungry.
- Nginx (Event-Driven): One process handles thousands of requests via async I/O.
Selection Guide
Choose Processes When:
- You need high isolation (security/stability).
- The task is CPU-bound (in Python/Ruby with GIL).
- Failure in one unit shouldn't crash the system.
Choose Threads When:
- You need to share a lot of data.
- High frequency communication is required.
- Creating thousands of units (though async/coroutines are better here).
- The task is I/O bound.
Interview Tips 💡
- "Is it better to use threads or processes?" — Always answer "It depends". Mention isolation vs sharing.
- "What is the GIL?" — Explain the limits of bytecode execution in a GIL-enabled CPython build and the exceptions for native code and free-threaded builds.
- "User vs Kernel Threads" — Kernel threads are managed by OS (expensive). User threads (Green threads/Goroutines) are managed by runtime (cheap).
- "Copy-on-Write (COW)" — Explain how
fork()is optimized by sharing memory pages until a write occurs.
Related Concepts
Source and practical check
Python threading documentation covers the GIL and free-threaded builds. Compare the same workload with threads and processes, including startup, serialization, shared-state locking, and memory use. Neither model universally wins; an I/O-bound test and a Python-bytecode CPU test answer different questions.
Keep reading
Concurrency Patterns
Essential patterns for managing concurrent execution. Mutex, Semaphores, Monitors, and modern approaches like the Actor Model and CSP.
Backpressure: Bound Work Before It Becomes a Backlog
Calculate queue growth, propagate bounded demand, and choose an explicit overflow policy using a runnable asynchronous queue example.
Cache Eviction Policies
When the cache is full, something has to go. A comprehensive guide to LRU, LFU, ARC, and other replacement algorithms with implementation details.
Caching Overview
Estimate cache benefits and cold-cache risk, choose a cache boundary, and test freshness and failure behavior.
Content Delivery Networks: Cache Keys, Freshness and Origin Load
Choose safe cache policies, calculate origin offload, and test personalized responses, invalidation and cold-cache recovery.
Database Indexing
Deep dive into database indexing internals. How B-Trees work, Clustered vs Non-Clustered indexes, Composite Index best practices, and covering indexes.