Separate processes have separate GILs; the cost is startup, pickling, and IPC
Each Python process has its own interpreter, its own heap, and therefore its own GIL. Two processes can execute Python bytecode on two cores simultaneously with no shared lock, which is how multiprocessing achieves true CPU parallelism. The costs are real: process startup is much more expensive than thread startup, especially on Windows and macOS where spawn is the only start method and the module must be re-imported. Data passed between processes is pickled, so large objects and functions with unpicklable state are problematic. Results come back through pipes or shared memory, and communication has latency. Shared state must use multiprocessing primitives (Value, Array, Manager, Queue) or be avoided entirely in favor of message passing.
Start methods: fork on Linux (default until 3.13 moved toward forkserver), spawn on Windows and macOS. spawn re-imports the module.
Pickling: arguments and return values are serialized. Lambdas, open files, and connections cannot be sent.
IPC: Queue and Pipe are convenient but slower than in-process calls. Shared memory (multiprocessing.shared_memory) avoids copy for large buffers.
Pool and ProcessPoolExecutor amortize process startup across many tasks.
Trade-off: true parallelism versus serialization and process overhead. Small tasks can be slower than sequential.
Common mistake: assuming a global variable is shared across processes. Each process has its own copy.
Common mistake: forgetting the if name == 'main' guard with spawn, causing infinite process spawning.
Version note: fork became non-default on Linux in 3.14 for safety with threads. 3.8 added multiprocessing.shared_memory for zero-copy buffers.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience