OPERATING SYSTEMS / 4. PROCESSES & THREADS
Processes, Threads & Concurrency
How the OS runs many programs at once on a few cores
EXPLANATION
The illusion your OS creates is that every program has the entire CPU to itself. In reality, the OS switches between programs thousands of times per second so fast it feels simultaneous. Process vs Thread: • A process is an isolated running program with its own virtual address space, file descriptors, and at least one thread • A thread is a unit of execution within a process. Multiple threads share the process's address space, file descriptors, and heap — but each has its own stack and registers • Creating a process (fork) is expensive — copies the address space, file descriptor table • Creating a thread is cheap — just allocates a new stack and kernel thread structure Why threads? Parallelism and responsiveness: • A web server with one thread can only handle one request at a time. With 100 threads, it handles 100 simultaneously • A UI app needs one thread for the UI (must respond to clicks) and another for background work (download, process) Thread safety and races: • If two threads read/write a shared variable without coordination → race condition → undefined behavior • Solution: mutual exclusion. Mutex (lock) ensures only one thread executes a critical section at a time • Lock → do work → unlock • Deadlock: Thread A holds Lock 1, waits for Lock 2. Thread B holds Lock 2, waits for Lock 1. Both wait forever. Python's GIL (Global Interpreter Lock): • CPython has a GIL — only one thread executes Python bytecode at a time • Threading in Python gives concurrency (great for I/O-bound work: HTTP requests, DB queries) • But NOT true parallelism for CPU-bound work • For CPU parallelism in Python: use multiprocessing (separate processes, no GIL) or async/await for I/O The Scheduler — how the OS decides who runs: • Linux uses CFS (Completely Fair Scheduler) • Each process has a priority (nice value, -20 to +19). Lower = higher priority • CFS tracks "virtual runtime" — how much CPU each process has used • Always runs the process with the least virtual runtime • Preemption: timer interrupt fires → scheduler checks → may switch to another process Context switch cost: ~1-10 µs. Involves saving all CPU registers, TLB flush, cache effects. Too many context switches = performance degradation. Process isolation mechanisms: • Virtual memory → different page tables → cannot access each other's memory • Capabilities → fine-grained permissions (open network sockets, access /dev, etc) • namespaces → what the process can see (network, PID, mount, user — this is how Docker works) • cgroups → limits on resources (CPU%, RAM, I/O) — also how Docker enforces container limits
DIAGRAM
PROCESS vs THREAD: ┌─────────────────────────────────────────────┐ │ Process │ │ Virtual Address Space │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Thread 1 │ │ Thread 2 │ │ Thread 3 │ │ │ │ Stack │ │ Stack │ │ Stack │ │ │ │ Registers│ │ Registers│ │ Registers│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ └────────────┴────────────┘ │ │ ↓ │ │ Shared: Heap, Code, Files │ └─────────────────────────────────────────────┘ SCHEDULER (CFS — Completely Fair Scheduler): Timeline: Core 0: [Process A]─[Process B]─[Process A]─[Process C]─... Core 1: [Process D]─[Process E]─[Process D]─[Process E]─... Each slice: ~1-4ms Switch triggered by: timer interrupt, blocking syscall, yield CONTEXT SWITCH: Running A → Interrupted → Save A's registers to PCB → Load B's registers from PCB → Run B
CODE