Concurrency Comparisons

Side-by-side trade-offs where neither column wins. The workload, the runtime and how much correctness risk you can carry decide — and each comparison ends with the verdict that follows from that, not from a preference.

Threads vs processes

Both give you parallel execution. They differ in what they share, and everything else — cost, isolation, failure blast radius, how you pass data — follows from that one difference.

DimensionThreadsProcesses
Address spaceShared — every object is visible to every threadSeparate — nothing is shared unless you arrange it
Data sharing costFree (a pointer), but every share needs synchronizationSerialize, copy, deserialize on every message
Creation costCheap: a stack and a scheduler entryExpensive: a full address space and runtime startup
IsolationNone — a corrupting write reaches everythingStrong — a crash takes down one worker
Crash blast radiusThe whole processOne worker; the supervisor restarts it
CPython bytecode parallelismSerialized by the GIL in standard buildsGenuinely parallel
DebuggingRaces, deadlocks, visibility bugs across the shared heapMessage ordering and lost messages; no shared-memory races
Memory footprintOne heap, many stacksOne heap per process — the multiplier people forget
Use Threads when
  • The work genuinely needs to share a large mutable structure.
  • Task creation is frequent and each task is short.
  • The runtime executes threads in parallel and you are prepared to synchronize.
Use Processes when
  • You need isolation: untrusted code, native crashes, leaky libraries.
  • You are on CPython and the work is CPU-bound.
  • The data passed per task is small relative to the compute per task.
Verdict

Default to processes when the tasks are independent — you trade copies for the elimination of an entire bug class. Reach for threads when the shared structure is genuinely large and hot, and accept that you have taken on the synchronization burden in exchange.