Explanation
What just happened
multiprocessing.Pool(2) started two worker processes. Each is a separate Python interpreter with its own GIL. pool.map pickled one name to each worker, both crunched at the same time on two cores, and the results came back pickled. main printed two different process IDs, neither of them its own.
Our tests measure it: the two workers use two CPUs’ worth of time at once, which threads with the GIL never do.
Try the what-if switch
- globals: each worker bumped a global
count, andmainstill printed 0. Processes don’t share memory: a worker has its own copy ofcount.
Why the __main__ guard
With the default start methods (spawn on macOS, forkserver on Linux since Python 3.14), a new worker process starts by importing your main script. If creating the pool isn’t behind if __name__ == "__main__":, the import tries to start a pool inside the worker. Python refuses, every worker crashes, and map never returns.
Watch out
Every argument and result is pickled and copied between processes. For many small tasks that copying can cost more than it saves.
Common questions
Why do I need if __name__ == '__main__' with multiprocessing?
With the default start methods (spawn on macOS, forkserver on Linux), each worker process imports your main script. Without the guard, every worker tries to start a pool too and crashes with a RuntimeError about the bootstrapping phase, and map never returns.
Why doesn't my global variable change with multiprocessing?
Each worker is a separate process with its own copy of every global. A worker changes its copy; the main process's copy stays the same. Send data back as return values.
Why can't I use a lambda with Pool.map?
Everything sent to a worker is pickled, and a lambda can't be pickled: pool.map(lambda x: x, ...) raises PicklingError. Use a function defined at the top level of a module.
Verified: this timeline matches a real program, checked in CI on Python 3.14, 3.15 and free-threaded 3.15t. How we verify. Unclear or wrong? Tell us