Concurrency line
Processes vs threads
Cooks in one kitchen share a fridge, which is fast but risky, while separate kitchens are safer but must pass slips.
Stops explain each step. The film plays the whole ride.
- Writing a number
- A slip sent over
- Reading or a reply
- Something went wrong
Cook 1 puts a number in the fridge
Kitchen A has two cooks who share one fridge. Cook 1 puts a number in it. Kitchen B is next door; slips pass through the window.
Next stop: Cook 2 reads it
All stops
The everyday version
Each kitchen has its own fridge. Cooks in one kitchen share it. Kitchens talk only through the pass window, using slips.
- Cook 1is the Thread 1
Works in Kitchen A. Can use anything in the shared fridge.
- Cook 2is the Thread 2
Same kitchen, same fridge. Sees Cook 1’s changes at once.
- Kitchen A’s fridgeis the Memory A
Open to every cook in Kitchen A. Handy, but one cook’s mess spoils it for all.
- The pass windowis the Pipe / Queue
The only way for the kitchens to talk. You pass a copy on a slip, never the fridge.
- Kitchen B’s cookis the Process B
Works in Kitchen B with its own rules. Reads your slip and writes its own.
- Kitchen B’s fridgeis the Memory B
Private to Kitchen B. Cooks from Kitchen A cannot reach it.
- A fire in the kitchenis a crash
If a cook starts a fire, the whole kitchen and everyone’s food are lost. A fire next door stays next door.
Interview questions
Say the short answer first. Go deeper only if they ask.
What are the key differences between a process and a thread?
Processes have isolated memory. Threads share the memory of their process.
A process is an OS resource container (address space, file descriptors). Threads are scheduling units inside it: they share the heap, globals and descriptors, but each has its own stack and registers.
“Threads are just lightweight processes that share nothing.”
Why is creating or switching threads cheaper?
There is no new address space to build or switch.
Creating a process allocates page tables and starts an interpreter (spawn) or copies mappings (fork). Switching between processes also switches the address space. Threads skip all of that.
“They cost the same.”
How do processes communicate?
Through IPC: pipes, sockets, queues or shared memory.
In Python, multiprocessing.Queue and Pipe pickle objects. shared_memory and Value/Array avoid the copy. A Manager proxies objects through a server process, which is slower.
“They just share variables.”
What happens if one thread crashes?
It depends. An exception kills only that thread. A segfault kills the whole process.
Python exceptions stay inside their thread. Fatal errors in C code, os._exit or an OOM kill take down every thread of that process.
“The other threads always carry on normally.”
Why can you not just share a Python list between processes?
Each process has its own copy of memory, so changes are not visible.
With fork, a child starts with a copy-on-write copy. With spawn or forkserver it rebuilds from imports. Sharing needs a Manager, shared memory or message passing.
“Pass the list as an argument and mutate it.”
When would you pick processes over threads in Python?
For CPU-bound work, or when you need fault isolation.
Each process has its own GIL, so CPU-bound code scales across cores. A crash or leak stays contained and the worker can be recycled (maxtasksperchild). The cost is pickling and memory per process.
“Always processes, since they are more real.”
What does fork do to threads?
Only the calling thread exists in the child.
A lock held by another thread at fork time stays locked forever in the child, a classic deadlock. That is why Python warns about fork with threads, and 3.14 moved the default away from it.
“Fork copies everything, including all threads.”
Does Python 3.14 change the default start method?
Yes. On Linux the default is now forkserver.
forkserver starts a single-threaded server and forks clean children from it, avoiding the multithreaded-fork hazard. Targets and arguments must be picklable and importable, as with spawn.
“Linux still defaults to fork.”
Cheat-sheet
Commands you will actually type.
import threading
threading.Thread(target=f, args=(1,)).start()A thread: shares memory, cheap to start.
import multiprocessing as mp
if __name__ == "__main__":
ctx = mp.get_context("spawn")
p = ctx.Process(target=f)
p.start(); p.join()A process with an explicit start method.
q = mp.Queue()
q.put({"a": 1})
q.get()Message passing between processes. The object arrives as a pickled copy.
from multiprocessing import shared_memory
shm = shared_memory.SharedMemory(create=True, size=1024)Zero-copy shared bytes across processes. Remember `shm.close()` and `shm.unlink()`.
p.exitcode # None=running, 0=ok, -N=killed by signal NDetect a crashed child process.
import os, threading
os.getpid(), threading.get_ident()Threads share a pid. Processes do not.
ps -M <pid> # macOS
ps -T -p <pid> # LinuxList the threads of a process.