Start methods explain most cross-platform multiprocessing bugs, from missing globals to hangs.
The multiprocessing module mirrors threading: Process(target=f, args=...), start(), join(), plus Pool with map, imap_unordered and apply_async. Check p.exitcode after join(): 0 means success, negative means killed by a signal.
How a child process starts matters:
- spawn: start a fresh interpreter that imports your main module, then unpickles the target. Safe and portable, slower to start. Default on Windows and macOS.
- fork: copy the parent process (POSIX only). Fast, and the child inherits all memory, including globals set at runtime. But forking a process that has threads is unsafe: locks held by other threads are copied in a locked state and can deadlock the child. Default on Linux through 3.13.
- forkserver: a clean server process forks children on request; safer than fork, faster than spawn. Python 3.14 makes it the default on Linux.
Choose explicitly for predictable behaviour:
ctx = multiprocessing.get_context("spawn")
with ctx.Pool(4) as pool: ...ProcessPoolExecutor(mp_context=ctx) accepts the same context. Code that "works on Linux but not Mac" usually relied on fork inheriting globals; pass state explicitly as arguments or via an initializer instead.
Example
import multiprocessing as mp
import os
CONFIG = {"mode": "default"}
def show(tag):
print(f"{tag} child {os.getpid() % 1000}: mode={CONFIG['mode']}")
def square(n):
return n * n
if __name__ == "__main__":
CONFIG["mode"] = "changed at runtime"
print("default method:", mp.get_start_method()) # fork on Linux 3.13, spawn on macOS/Windows
for method in ("fork", "spawn"):
ctx = mp.get_context(method)
p = ctx.Process(target=show, args=(method,))
p.start(); p.join()
print(method, "exitcode", p.exitcode)
with mp.get_context("spawn").Pool(2) as pool:
print(pool.map(square, range(5))) # [0, 1, 4, 9, 16]
# fork child ...: mode=changed at runtime (parent memory copied)
# spawn child ...: mode=default (module re-imported fresh)Remember
- multiprocessing.Process and Pool mirror the threading API
- spawn re-imports your module; fork copies memory; forkserver forks from a clean server
- Pick a start method explicitly with get_context()
Watch out for
- Relying on fork copying runtime-modified globals, then breaking on macOS or Windows where spawn is the default.