Threads vs Processes: Shared vs Isolated State
What This Concept Is
A process has its own address space, open-file table, signal state, credentials, and PID. A thread is a schedulable execution within a process. Multiple threads of one process share:
| State | Shared across threads? | Shared across processes? |
|---|---|---|
| Address space (code, heap, globals) | Yes | No |
| Stack | No (each thread has its own) | No |
| File descriptor table | Yes | No (but copied on fork) |
| Signal handlers | Yes | No |
| Signal mask | No (per-thread) | No |
| Current directory, umask | Yes | No |
| PID | Same | Different |
| TID (thread id) | Different | Different |
| Registers (PC, SP) | Different | Different |
| Credentials, effective UID | Usually shared | No |
On Linux, clone() is the primitive. Flags (CLONE_VM, CLONE_FILES, CLONE_SIGHAND, CLONE_THREAD) select what to share. pthread_create passes flags for "thread-like" sharing; fork passes none (full copy).
Why It Matters Here
"Thread" and "process" are not genres of thing; they are points on a spectrum of "how much state do you share." The decision is an engineering tradeoff:
- Sharing is cheap communication (read a shared variable) and cheap switching (no
CR3reload, no TLB flush). - Isolation is crash containment (one process segfault doesn't take the others) and security (no shared memory -> no shared exploits).
The choice affects scheduling cost (Concept 11), synchronization strategy (Module 3), and failure modes (observability).
Concrete Example
Same problem, two shapes:
A. Multi-process web server (prefork, e.g., Apache mpm_prefork).
- Master
forksNworker processes. - Workers share code pages (read-only, COW), not heap.
- A worker segfault kills itself, master respawns it; other workers keep serving.
- Communication between workers: shared memory segments or a pipe.
B. Multi-threaded web server (e.g., nginx workers internally, or a Java app server).
- One process,
Nthreads, one address space, one connection table. - Segfault in one thread crashes the whole process.
- Communication between threads: shared data structures under a lock.
Tradeoff summary:
| Question | Multi-process | Multi-threaded |
|---|---|---|
| Context switch cost | Higher (address space change) | Lower |
| Crash blast radius | Contained | Whole process |
| Data sharing | Explicit (shm, pipes) | Implicit (just read) |
| Security boundary | Yes | No |
| Debuggability | Easier (isolated) | Harder (race conditions) |
Common Confusion / Misconception
"Threads are just lightweight processes, use them when you need speed." That framing sells threads short on safety and over-sells them on speed. If the workloads do not share state, processes can be faster in practice (no locking, no false sharing of cache lines).
"fork is always slow." With copy-on-write it is actually quite fast -- often faster than setting up a thread pool from scratch -- as long as the child promptly execs or uses only a small working set.
How To Use It
When choosing, ask the questions in this order:
- Do the units of work share significant mutable state? If no -> prefer processes.
- Is fault isolation important? If yes -> prefer processes.
- Is the switch rate high and cache-sensitive? If yes -> threads can help.
- Is portability across non-POSIX platforms needed? If yes -> threads (via a threading runtime) are more portable.
Async (epoll, futures) is a third option for many I/O-bound workloads and should be considered before either.
Check Yourself
- Two threads in the same process each have their own stack. Why must they?
- Why does a signal sent with
killtarget a process, whilepthread_killtargets a thread? - What does
CLONE_VMwithoutCLONE_FILESproduce?
Mini Drill or Application
- Write a program that creates 10 threads, each running a counter loop, and sum their results.
- Rewrite it as 10 processes using
forkand pipes. - Measure total wall time and max RSS for each.
- Write one paragraph explaining the ranking and what would change on a 128-core machine.