Module 03 · Processes and threads · Lesson 2 of 7
Threads vs processes
Threads share their process's memory and files while each keeps its own stack and registers. What that sharing buys (cheap communication, low overhead) and costs (races, one crash kills all), kernel versus user-level threads.
Why it matters
"Should this be a thread or a process?" sits under every concurrency decision: a web server's worker model, a browser's architecture, why Python uses multiprocessing for CPU work, why Go can run a million goroutines. The answer depends on what you want shared, what you want isolated, and how much each option costs, and interviewers use the question to test whether you understand both.
The idea
A thread is an independent stream of execution inside a process. All threads of a process share its address space, heap, open files and credentials; each thread has its own stack, registers and program counter. So threads can communicate by simply reading and writing the same memory, which is fast and also the source of every race condition.
| Threads in one process | Separate processes | |
|---|---|---|
| Memory | Shared heap; private stacks | Fully private |
| Communication | Read and write shared variables, guarded by locks | Pipes, sockets, shared memory segments, message queues |
| Creation cost | Tens of microseconds; a stack (often 1 to 8 MB reserved) | Higher: new address space and kernel structures |
| Switching cost | Cheaper: same address space, caches and TLB stay warm | Dearer: address space switch, TLB flush on many CPUs |
| Failure | A crash or memory corruption takes down every thread | Isolated: one process dies, the rest continue |
| Security boundary | None between threads | Yes; the kernel enforces it |
| Typical use | Request handling within a server, parallel computation on shared data | Workers that must not affect each other, sandboxes, separate services |
Concurrency and parallelism
Concurrency is about structure: several tasks in progress at once, interleaved. Parallelism is about execution: several tasks literally running at the same instant on different cores. Threads give you both when the runtime allows it. CPython's global interpreter lock is the well-known exception: Python threads interleave but only one runs Python bytecode at a time, so CPU-bound Python code uses processes (multiprocessing) for parallelism, while I/O-bound code can still benefit from threads because the lock is released while waiting. (Recent CPython versions offer an optional build without the lock.)
Kernel threads and user-level threads
- 1
Kernel threads (one-to-one)
Each thread is known to and scheduled by the kernel. True parallelism, blocking calls block only that thread, and creation and switching go through the kernel. Java's platform threads and pthreads on Linux work this way. - 2
User-level threads (many-to-one)
A library schedules many lightweight threads on one kernel thread. Very cheap to create and switch, but a blocking system call stalls all of them and they cannot use more than one core. Early green-thread systems worked this way. - 3
Many-to-many (M:N)
A runtime multiplexes many lightweight threads over a pool of kernel threads, parking those that block on I/O and running others. Go's goroutines, Java's virtual threads and Erlang processes take this approach, which is how a single server can hold hundreds of thousands of concurrent tasks with small, growable stacks.
Choosing
Use threads, or a runtime's lightweight threads, for concurrent work on shared data inside one service: handling requests, parallel loops, background tasks. Use processes when failures must not spread, when untrusted code must be sandboxed, when you need parallelism in a runtime with a global lock, or when components are deployed and scaled independently. Many servers combine them: a few processes (often one per core or per container) each running many threads or an event loop.
Trade-offs
Threads trade isolation for speed of communication and lower overhead, and hand you the hardest class of bugs (races, deadlocks, memory corruption shared by all). Processes trade speed for safety. Lightweight thread runtimes reduce the cost of having many threads, but not the cost of sharing memory unsafely.
In an interview
Tie the choice to failure and sharing: "Image processing runs in separate worker processes because a malformed file can crash the decoder and we do not want that to take down request handling. Request handling itself uses a thread pool sized to the cores, sharing an in-memory cache." For high concurrency: "With mostly idle connections we would use goroutines or virtual threads rather than a platform thread per connection."
Check yourself
A Python service does CPU-heavy image resizing. Moving the work from 1 thread to 8 threads on an 8-core machine barely improves throughput. Why, and what helps?